react-cheminfo 0.48.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 +25 -2
- package/bin/check-seo.mjs +30 -4
- 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/routes.d.ts +1 -1
- package/lib/seo/core/routes.js +1 -1
- 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/lib/seo/vite/prerender.d.ts +6 -0
- package/lib/seo/vite/prerender.d.ts.map +1 -1
- package/lib/seo/vite/prerender.js +7 -1
- package/lib/seo/vite/prerender.js.map +1 -1
- package/package.json +2 -1
- package/src/pages.ts +2 -0
- package/src/seo/core/routes.ts +1 -1
- package/src/seo/core/trackingMiddleware.ts +48 -0
- package/src/seo/vite/prerender.ts +7 -1
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 |
|
|
@@ -1043,7 +1044,7 @@ keep in step.
|
|
|
1043
1044
|
```ts
|
|
1044
1045
|
cheminfoPrerender({ site: 'surge', routes: PAGE_ROUTES });
|
|
1045
1046
|
// origin defaults to https://surge.cheminfo.org — the site owns its host,
|
|
1046
|
-
// the mount is '', and /exercises is written at dist/exercises
|
|
1047
|
+
// the mount is '', and /exercises is written at dist/exercises.html.
|
|
1047
1048
|
|
|
1048
1049
|
cheminfoPrerender({
|
|
1049
1050
|
site: 'surge',
|
|
@@ -1055,7 +1056,7 @@ cheminfoPrerender({
|
|
|
1055
1056
|
```
|
|
1056
1057
|
|
|
1057
1058
|
The **files on disk are laid out from the build's own root either way** —
|
|
1058
|
-
`dist/exercises
|
|
1059
|
+
`dist/exercises.html`, never `dist/surge/exercises.html`. It is the
|
|
1059
1060
|
server that puts them under the mount. An origin must be an absolute `http` or
|
|
1060
1061
|
`https` address or it is refused: one written `localhost:3000` parses with
|
|
1061
1062
|
`localhost:` as its scheme, which would read the mount back as `/3000`.
|
|
@@ -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,10 +60,11 @@ 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) {
|
|
66
|
-
process.exit(usageError(TOOL, `no
|
|
67
|
+
process.exit(usageError(TOOL, `no HTML page under ${root}`));
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
const problems = [];
|
|
@@ -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
|
*/
|
|
@@ -367,9 +373,29 @@ function collect(path, pages) {
|
|
|
367
373
|
const next = join(path, entry.name);
|
|
368
374
|
if (entry.isDirectory()) {
|
|
369
375
|
collect(next, pages);
|
|
370
|
-
} else if (entry.name
|
|
371
|
-
const
|
|
372
|
-
|
|
376
|
+
} else if (entry.name.endsWith('.html')) {
|
|
377
|
+
const page = relative(root, next).replace(/\.html$/, '');
|
|
378
|
+
const address = trimSlash(`/${page.replace(/(?:^|\/)index$/, '')}`);
|
|
379
|
+
if (entry.name === 'index.html' || named.has(address)) {
|
|
380
|
+
pages.push({ file: next, address });
|
|
381
|
+
}
|
|
373
382
|
}
|
|
374
383
|
}
|
|
375
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"}
|
package/lib/seo/core/routes.d.ts
CHANGED
|
@@ -113,7 +113,7 @@ export declare function homeRoute(routes: readonly RouteMeta[]): RouteMeta;
|
|
|
113
113
|
* only the first entry describes, and one carrying a `..` segment writes its
|
|
114
114
|
* file outside the build output. Two addresses that differ only in an empty
|
|
115
115
|
* segment or in case are the same defect wearing a disguise: `//x` and `/x`
|
|
116
|
-
* both write `dist/x
|
|
116
|
+
* both write `dist/x.html`, and so do `/About` and `/about` on the
|
|
117
117
|
* case-insensitive filesystem macOS and Windows ship by default — one file, two
|
|
118
118
|
* sitemap entries, and only one of the two descriptions survives. All of it is
|
|
119
119
|
* author configuration read at build time, so it is refused where it is written
|
package/lib/seo/core/routes.js
CHANGED
|
@@ -93,7 +93,7 @@ export function homeRoute(routes) {
|
|
|
93
93
|
* only the first entry describes, and one carrying a `..` segment writes its
|
|
94
94
|
* file outside the build output. Two addresses that differ only in an empty
|
|
95
95
|
* segment or in case are the same defect wearing a disguise: `//x` and `/x`
|
|
96
|
-
* both write `dist/x
|
|
96
|
+
* both write `dist/x.html`, and so do `/About` and `/about` on the
|
|
97
97
|
* case-insensitive filesystem macOS and Windows ship by default — one file, two
|
|
98
98
|
* sitemap entries, and only one of the two descriptions survives. All of it is
|
|
99
99
|
* author configuration read at build time, so it is refused where it is written
|
|
@@ -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"}
|
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
* Every address the tool answers is on disk, so an address that is *not* on
|
|
16
16
|
* disk is genuinely not a page and must 404 rather than serving the tool under
|
|
17
17
|
* a name it does not have.
|
|
18
|
+
*
|
|
19
|
+
* `/about` is written to `about.html`, never `about/index.html`: a static host
|
|
20
|
+
* serves a folder's index at `/about/` and redirects the address every link and
|
|
21
|
+
* canonical uses to it (Cloudflare Pages does, with no way to turn it off),
|
|
22
|
+
* while `about.html` is served at `/about` itself by Pages, static-web-server
|
|
23
|
+
* and `vite preview` alike.
|
|
18
24
|
*/
|
|
19
25
|
import type { Plugin } from 'vite';
|
|
20
26
|
import type { SiteId, SiteRecord } from '../../ecosystem/core/sites.ts';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prerender.d.ts","sourceRoot":"","sources":["../../../src/seo/vite/prerender.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"prerender.d.ts","sourceRoot":"","sources":["../../../src/seo/vite/prerender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAKH,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,MAAM,CAAC;AAE3C,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAExE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAI7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAMnD,kEAAkE;AAClE,MAAM,WAAW,gBAAgB;IAC/B,iCAAiC;IACjC,IAAI,EAAE,UAAU,GAAG,MAAM,CAAC;IAC1B;;;;;;;;;OASG;IACH,MAAM,EAAE,SAAS,SAAS,EAAE,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,KAAK,SAAS,SAAS,EAAE,CAAC,CAAC;IAC9E;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,KAAK,GAAG,aAAa,CAAC,MAAM,GAAG,cAAc,CAAC,CAAC;IACxD;;;;OAIG;IACH,QAAQ,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC1B;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;OAOG;IACH,QAAQ,CAAC,EACP,OAAO,GAAG,YAAY,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,KAAK,OAAO,GAAG,YAAY,CAAC,CAAC;IAC5E;;;;;;;;;;OAUG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,KAAK,WAAW,GAAG,SAAS,CAAC;IAC5E;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;CACjC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CA2HnE"}
|
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
* Every address the tool answers is on disk, so an address that is *not* on
|
|
16
16
|
* disk is genuinely not a page and must 404 rather than serving the tool under
|
|
17
17
|
* a name it does not have.
|
|
18
|
+
*
|
|
19
|
+
* `/about` is written to `about.html`, never `about/index.html`: a static host
|
|
20
|
+
* serves a folder's index at `/about/` and redirects the address every link and
|
|
21
|
+
* canonical uses to it (Cloudflare Pages does, with no way to turn it off),
|
|
22
|
+
* while `about.html` is served at `/about` itself by Pages, static-web-server
|
|
23
|
+
* and `vite preview` alike.
|
|
18
24
|
*/
|
|
19
25
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
20
26
|
import { dirname, join, resolve } from 'node:path';
|
|
@@ -105,7 +111,7 @@ export function cheminfoPrerender(options) {
|
|
|
105
111
|
root = true;
|
|
106
112
|
write(route, language, address === '/'
|
|
107
113
|
? join(out, 'index.html')
|
|
108
|
-
: join(out, address.slice(1)
|
|
114
|
+
: join(out, `${address.slice(1)}.html`));
|
|
109
115
|
written++;
|
|
110
116
|
}
|
|
111
117
|
// The file a static server hands out for the mount itself. A table
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prerender.js","sourceRoot":"","sources":["../../../src/seo/vite/prerender.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"prerender.js","sourceRoot":"","sources":["../../../src/seo/vite/prerender.ts"],"names":[],"mappings":"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"}
|
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
package/src/seo/core/routes.ts
CHANGED
|
@@ -156,7 +156,7 @@ export function homeRoute(routes: readonly RouteMeta[]): RouteMeta {
|
|
|
156
156
|
* only the first entry describes, and one carrying a `..` segment writes its
|
|
157
157
|
* file outside the build output. Two addresses that differ only in an empty
|
|
158
158
|
* segment or in case are the same defect wearing a disguise: `//x` and `/x`
|
|
159
|
-
* both write `dist/x
|
|
159
|
+
* both write `dist/x.html`, and so do `/About` and `/about` on the
|
|
160
160
|
* case-insensitive filesystem macOS and Windows ship by default — one file, two
|
|
161
161
|
* sitemap entries, and only one of the two descriptions survives. All of it is
|
|
162
162
|
* author configuration read at build time, so it is refused where it is written
|
|
@@ -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
|
+
}
|
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
* Every address the tool answers is on disk, so an address that is *not* on
|
|
16
16
|
* disk is genuinely not a page and must 404 rather than serving the tool under
|
|
17
17
|
* a name it does not have.
|
|
18
|
+
*
|
|
19
|
+
* `/about` is written to `about.html`, never `about/index.html`: a static host
|
|
20
|
+
* serves a folder's index at `/about/` and redirects the address every link and
|
|
21
|
+
* canonical uses to it (Cloudflare Pages does, with no way to turn it off),
|
|
22
|
+
* while `about.html` is served at `/about` itself by Pages, static-web-server
|
|
23
|
+
* and `vite preview` alike.
|
|
18
24
|
*/
|
|
19
25
|
|
|
20
26
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
@@ -229,7 +235,7 @@ export function cheminfoPrerender(options: PrerenderOptions): Plugin {
|
|
|
229
235
|
language,
|
|
230
236
|
address === '/'
|
|
231
237
|
? join(out, 'index.html')
|
|
232
|
-
: join(out, address.slice(1)
|
|
238
|
+
: join(out, `${address.slice(1)}.html`),
|
|
233
239
|
);
|
|
234
240
|
written++;
|
|
235
241
|
}
|