react-cheminfo 0.49.0 → 0.50.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/README.md +23 -0
- package/bin/check-seo.mjs +27 -2
- package/lib/pages.d.ts +3 -0
- package/lib/pages.d.ts.map +1 -0
- package/lib/pages.js +2 -0
- package/lib/pages.js.map +1 -0
- package/lib/seo/core/trackingMiddleware.d.ts +27 -0
- package/lib/seo/core/trackingMiddleware.d.ts.map +1 -0
- package/lib/seo/core/trackingMiddleware.js +33 -0
- package/lib/seo/core/trackingMiddleware.js.map +1 -0
- package/package.json +2 -1
- package/src/pages.ts +2 -0
- package/src/seo/core/trackingMiddleware.ts +48 -0
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ only when it drags a heavy dependency behind it.
|
|
|
35
35
|
| `react-cheminfo/core` | every framework-free helper — 140 value exports | nothing |
|
|
36
36
|
| `react-cheminfo/ui` | every React component and hook — 60 value exports | React |
|
|
37
37
|
| `react-cheminfo/vite` | the prerender plugin and the OG card | nothing; React on the card |
|
|
38
|
+
| `react-cheminfo/pages` | the Cloudflare Pages analytics middleware | nothing |
|
|
38
39
|
| `react-cheminfo/orbital` | the 3D atomic-orbital viewer | React, molstar |
|
|
39
40
|
| `react-cheminfo/molecule3d` | the 3D molecule viewer and its toolbar | React, molstar |
|
|
40
41
|
| `react-cheminfo/structure` | the structure editor and renderer | React, react-ocl, OCL |
|
|
@@ -1096,6 +1097,28 @@ disk. A server writing more into the same place composes it itself, with
|
|
|
1096
1097
|
`pageHeadTags` and `fill(page, PAGE_BODY_MARKER, noscriptIndex(…))`. All of that
|
|
1097
1098
|
is `react-cheminfo/core`, so a backend loads no React and no Vite.
|
|
1098
1099
|
|
|
1100
|
+
### On Cloudflare Pages
|
|
1101
|
+
|
|
1102
|
+
Pages serves the build as it is, so there is no server and no entrypoint to put
|
|
1103
|
+
the analytics snippet in a page. `onRequest` from `react-cheminfo/pages` adds it
|
|
1104
|
+
per request instead, from the project's `TRACKING_SCRIPT` variable, and returns
|
|
1105
|
+
everything that is not a page untouched:
|
|
1106
|
+
|
|
1107
|
+
```ts
|
|
1108
|
+
// functions/_middleware.ts
|
|
1109
|
+
export { onRequest } from 'react-cheminfo/pages';
|
|
1110
|
+
```
|
|
1111
|
+
|
|
1112
|
+
It is a door of its own so the Functions bundler reads two small files and
|
|
1113
|
+
never the rest of `core`, whose JSON imports an older esbuild cannot parse.
|
|
1114
|
+
|
|
1115
|
+
Keep the bundles out of the function with a `public/_routes.json`, so a page
|
|
1116
|
+
view costs one invocation rather than one per asset:
|
|
1117
|
+
|
|
1118
|
+
```json
|
|
1119
|
+
{ "version": 1, "include": ["/*"], "exclude": ["/assets/*"] }
|
|
1120
|
+
```
|
|
1121
|
+
|
|
1099
1122
|
## Saying which build is running
|
|
1100
1123
|
|
|
1101
1124
|
A report of something going wrong is worth answering only when we know what was
|
package/bin/check-seo.mjs
CHANGED
|
@@ -60,6 +60,7 @@ if (stats === undefined || !stats.isDirectory()) {
|
|
|
60
60
|
process.exit(usageError(TOOL, `no such build directory: ${root}`));
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
const named = sitemapAddresses();
|
|
63
64
|
const pages = [];
|
|
64
65
|
collect(root, pages);
|
|
65
66
|
if (pages.length === 0) {
|
|
@@ -358,6 +359,11 @@ function exists(path) {
|
|
|
358
359
|
|
|
359
360
|
/**
|
|
360
361
|
* Add every built page under a path to the list, with the address it answers.
|
|
362
|
+
*
|
|
363
|
+
* A folder's `index.html` is always a page. Any other `.html` file is one only
|
|
364
|
+
* when the sitemap names its address, as every prerendered `<route>.html` is:
|
|
365
|
+
* a demo the build emits or a document copied from `public` is not a page of
|
|
366
|
+
* the site, and holding it to a page's head would report a correct build.
|
|
361
367
|
* @param {string} path - A directory of the build.
|
|
362
368
|
* @param {Array<{ file: string, address: string }>} pages - Collected so far.
|
|
363
369
|
*/
|
|
@@ -369,8 +375,27 @@ function collect(path, pages) {
|
|
|
369
375
|
collect(next, pages);
|
|
370
376
|
} else if (entry.name.endsWith('.html')) {
|
|
371
377
|
const page = relative(root, next).replace(/\.html$/, '');
|
|
372
|
-
const address = `/${page.replace(/(?:^|\/)index$/, '')}
|
|
373
|
-
|
|
378
|
+
const address = trimSlash(`/${page.replace(/(?:^|\/)index$/, '')}`);
|
|
379
|
+
if (entry.name === 'index.html' || named.has(address)) {
|
|
380
|
+
pages.push({ file: next, address });
|
|
381
|
+
}
|
|
374
382
|
}
|
|
375
383
|
}
|
|
376
384
|
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Read which addresses of this build the sitemap names.
|
|
388
|
+
* @returns {Set<string>} Every address of this build the sitemap names.
|
|
389
|
+
*/
|
|
390
|
+
function sitemapAddresses() {
|
|
391
|
+
const sitemap = join(root, 'sitemap.xml');
|
|
392
|
+
const addresses = new Set();
|
|
393
|
+
if (!exists(sitemap)) return addresses;
|
|
394
|
+
for (const match of readFileSync(sitemap, 'utf8').matchAll(
|
|
395
|
+
/<loc>(?<loc>[^<]+)<\/loc>/g,
|
|
396
|
+
)) {
|
|
397
|
+
const own = ownPath(match.groups?.loc ?? '');
|
|
398
|
+
if (own !== undefined) addresses.add(own === '' ? '/' : own);
|
|
399
|
+
}
|
|
400
|
+
return addresses;
|
|
401
|
+
}
|
package/lib/pages.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../src/pages.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAC;AAClF,OAAO,EAAE,wBAAwB,IAAI,SAAS,EAAE,MAAM,kCAAkC,CAAC"}
|
package/lib/pages.js
ADDED
package/lib/pages.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pages.js","sourceRoot":"","sources":["../src/pages.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,IAAI,SAAS,EAAE,MAAM,kCAAkC,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** What a Cloudflare Pages Function hands its middleware, as far as it is read. */
|
|
2
|
+
export interface TrackingMiddlewareContext {
|
|
3
|
+
/** The visitor's request. */
|
|
4
|
+
request: Request;
|
|
5
|
+
/** The project's variables; `TRACKING_SCRIPT` holds the analytics snippet. */
|
|
6
|
+
env: {
|
|
7
|
+
TRACKING_SCRIPT?: string;
|
|
8
|
+
};
|
|
9
|
+
/** Serve the request from the build, as Pages would without a function. */
|
|
10
|
+
next: () => Promise<Response>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Put the analytics snippet in every page a Cloudflare Pages site serves.
|
|
14
|
+
*
|
|
15
|
+
* Pages has no server and no entrypoint to write the deployment's snippet into
|
|
16
|
+
* the build, so it is added per request instead, from the project's
|
|
17
|
+
* `TRACKING_SCRIPT` variable — the deployment owns it, never the build. With
|
|
18
|
+
* the variable unset, and for anything that is not a page, the response the
|
|
19
|
+
* build gives is returned untouched. A site wires it in
|
|
20
|
+
* `functions/_middleware.ts`:
|
|
21
|
+
*
|
|
22
|
+
* export { onRequest } from 'react-cheminfo/pages';
|
|
23
|
+
* @param context - The Pages Function context.
|
|
24
|
+
* @returns The response, carrying the snippet when it is an HTML page.
|
|
25
|
+
*/
|
|
26
|
+
export declare function trackingScriptMiddleware(context: TrackingMiddlewareContext): Promise<Response>;
|
|
27
|
+
//# sourceMappingURL=trackingMiddleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trackingMiddleware.d.ts","sourceRoot":"","sources":["../../../src/seo/core/trackingMiddleware.ts"],"names":[],"mappings":"AAEA,mFAAmF;AACnF,MAAM,WAAW,yBAAyB;IACxC,6BAA6B;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,8EAA8E;IAC9E,GAAG,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClC,2EAA2E;IAC3E,IAAI,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC/B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,QAAQ,CAAC,CAmBnB"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { injectTrackingScript } from "./trackingScript.js";
|
|
2
|
+
/**
|
|
3
|
+
* Put the analytics snippet in every page a Cloudflare Pages site serves.
|
|
4
|
+
*
|
|
5
|
+
* Pages has no server and no entrypoint to write the deployment's snippet into
|
|
6
|
+
* the build, so it is added per request instead, from the project's
|
|
7
|
+
* `TRACKING_SCRIPT` variable — the deployment owns it, never the build. With
|
|
8
|
+
* the variable unset, and for anything that is not a page, the response the
|
|
9
|
+
* build gives is returned untouched. A site wires it in
|
|
10
|
+
* `functions/_middleware.ts`:
|
|
11
|
+
*
|
|
12
|
+
* export { onRequest } from 'react-cheminfo/pages';
|
|
13
|
+
* @param context - The Pages Function context.
|
|
14
|
+
* @returns The response, carrying the snippet when it is an HTML page.
|
|
15
|
+
*/
|
|
16
|
+
export async function trackingScriptMiddleware(context) {
|
|
17
|
+
const response = await context.next();
|
|
18
|
+
const snippet = context.env.TRACKING_SCRIPT?.trim() ?? '';
|
|
19
|
+
if (snippet === '' ||
|
|
20
|
+
context.request.method !== 'GET' ||
|
|
21
|
+
response.status === 304 ||
|
|
22
|
+
!response.headers.get('content-type')?.includes('text/html')) {
|
|
23
|
+
return response;
|
|
24
|
+
}
|
|
25
|
+
const headers = new Headers(response.headers);
|
|
26
|
+
headers.delete('content-length');
|
|
27
|
+
return new Response(injectTrackingScript(await response.text(), snippet), {
|
|
28
|
+
status: response.status,
|
|
29
|
+
statusText: response.statusText,
|
|
30
|
+
headers,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=trackingMiddleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trackingMiddleware.js","sourceRoot":"","sources":["../../../src/seo/core/trackingMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAY3D;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,OAAkC;IAElC,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;IACtC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC1D,IACE,OAAO,KAAK,EAAE;QACd,OAAO,CAAC,OAAO,CAAC,MAAM,KAAK,KAAK;QAChC,QAAQ,CAAC,MAAM,KAAK,GAAG;QACvB,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,EAC5D,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC9C,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACjC,OAAO,IAAI,QAAQ,CAAC,oBAAoB,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,EAAE;QACxE,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,OAAO;KACR,CAAC,CAAC;AACL,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-cheminfo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React components, hooks and framework-free helpers shared by the cheminfo sites: site chrome, citations, sharing, routing, pedagogy, charts, and the structure, orbital and 3D molecule viewers",
|
|
6
6
|
"author": "Luc Patiny",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"./molecule3d": "./lib/molecule3d.js",
|
|
23
23
|
"./molstar/*": "./lib/molstar/*/index.js",
|
|
24
24
|
"./orbital": "./lib/orbital.js",
|
|
25
|
+
"./pages": "./lib/pages.js",
|
|
25
26
|
"./slides": "./lib/slides.js",
|
|
26
27
|
"./spectra": "./lib/spectra.js",
|
|
27
28
|
"./structure": "./lib/structure.js",
|
package/src/pages.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { injectTrackingScript } from './trackingScript.ts';
|
|
2
|
+
|
|
3
|
+
/** What a Cloudflare Pages Function hands its middleware, as far as it is read. */
|
|
4
|
+
export interface TrackingMiddlewareContext {
|
|
5
|
+
/** The visitor's request. */
|
|
6
|
+
request: Request;
|
|
7
|
+
/** The project's variables; `TRACKING_SCRIPT` holds the analytics snippet. */
|
|
8
|
+
env: { TRACKING_SCRIPT?: string };
|
|
9
|
+
/** Serve the request from the build, as Pages would without a function. */
|
|
10
|
+
next: () => Promise<Response>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Put the analytics snippet in every page a Cloudflare Pages site serves.
|
|
15
|
+
*
|
|
16
|
+
* Pages has no server and no entrypoint to write the deployment's snippet into
|
|
17
|
+
* the build, so it is added per request instead, from the project's
|
|
18
|
+
* `TRACKING_SCRIPT` variable — the deployment owns it, never the build. With
|
|
19
|
+
* the variable unset, and for anything that is not a page, the response the
|
|
20
|
+
* build gives is returned untouched. A site wires it in
|
|
21
|
+
* `functions/_middleware.ts`:
|
|
22
|
+
*
|
|
23
|
+
* export { onRequest } from 'react-cheminfo/pages';
|
|
24
|
+
* @param context - The Pages Function context.
|
|
25
|
+
* @returns The response, carrying the snippet when it is an HTML page.
|
|
26
|
+
*/
|
|
27
|
+
export async function trackingScriptMiddleware(
|
|
28
|
+
context: TrackingMiddlewareContext,
|
|
29
|
+
): Promise<Response> {
|
|
30
|
+
const response = await context.next();
|
|
31
|
+
const snippet = context.env.TRACKING_SCRIPT?.trim() ?? '';
|
|
32
|
+
if (
|
|
33
|
+
snippet === '' ||
|
|
34
|
+
context.request.method !== 'GET' ||
|
|
35
|
+
response.status === 304 ||
|
|
36
|
+
!response.headers.get('content-type')?.includes('text/html')
|
|
37
|
+
) {
|
|
38
|
+
return response;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const headers = new Headers(response.headers);
|
|
42
|
+
headers.delete('content-length');
|
|
43
|
+
return new Response(injectTrackingScript(await response.text(), snippet), {
|
|
44
|
+
status: response.status,
|
|
45
|
+
statusText: response.statusText,
|
|
46
|
+
headers,
|
|
47
|
+
});
|
|
48
|
+
}
|