@zhin.js/page 1.1.0 → 1.1.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/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # @zhin.js/page
2
2
 
3
- 标准 Page Feature provider。它把插件或项目根的平面 `pages/*.ts|tsx` 发现为 owner-bound Page manifest,并从 Plugin path 确定性生成 route。
3
+ 标准 Page Feature provider。它把插件或项目根的平面 `pages/*/index.ts|tsx` 发现为 owner-bound Page manifest,并从 Plugin path 确定性生成 route;未加 `$` 的文件不会被加载为页面。
4
4
 
5
5
  ## Convention
6
6
 
7
7
  - `pages/status.tsx` -> localName `status`。
8
8
  - Root route 为 `/p-status`;`root/a/b` 的 route 为 `/a/b/p-status`。
9
9
  - 首版不递归,不支持 `index`、`[param]` 或 route metadata override。
10
- - `$nav.tsx`、`$footer.tsx` 以及其他 `$` 文件不属于 Page。
10
+ - `pages/nav/`、`pages/footer/` 由 Layout Feature 处理,不属于普通 Page。
11
11
 
12
12
  ## Client Boundary
13
13
 
package/lib/provider.js CHANGED
@@ -1,6 +1,6 @@
1
- import { join, parse } from 'node:path';
1
+ import { join, sep } from 'node:path';
2
2
  import { featureId } from '@zhin.js/plugin-runtime';
3
- import { defineFeatureProvider } from '@zhin.js/feature-kit';
3
+ import { defineFeatureProvider, } from '@zhin.js/feature-kit';
4
4
  import { parsePageArtifact } from './definition.js';
5
5
  import { PageIndex } from './page-index.js';
6
6
  export const pageFeatureId = featureId('zhin.page');
@@ -11,11 +11,20 @@ const pageFiles = {
11
11
  const entries = [...await context.host.list(directory)]
12
12
  .sort((left, right) => left.name.localeCompare(right.name));
13
13
  for (const entry of entries) {
14
- if (entry.kind !== 'file' || !isPageFile(entry.name))
14
+ if (entry.kind !== 'directory' || !isPageName(entry.name)
15
+ || entry.name === 'nav' || entry.name === 'footer')
16
+ continue;
17
+ const moduleDirectory = join(directory, entry.name);
18
+ const moduleEntries = await context.host.list(moduleDirectory);
19
+ const index = preferredPageIndex(moduleEntries, context.packageRoot.split(sep).includes('node_modules'));
20
+ if (!index)
15
21
  continue;
16
22
  yield {
17
- localName: parse(entry.name).name,
18
- source: join(directory, entry.name),
23
+ localName: entry.name,
24
+ source: join(moduleDirectory, index),
25
+ relatedSources: moduleEntries
26
+ .filter((candidate) => candidate.kind === 'file' && candidate.name !== index)
27
+ .map((candidate) => join(moduleDirectory, candidate.name)),
19
28
  target: 'client',
20
29
  };
21
30
  }
@@ -40,8 +49,15 @@ const pageFeature = defineFeatureProvider({
40
49
  },
41
50
  },
42
51
  });
43
- function isPageFile(value) {
44
- return /^[a-z0-9][a-z0-9-]*\.tsx?$/u.test(value);
52
+ function isPageName(value) {
53
+ return /^[a-z0-9][a-z0-9-]*$/u.test(value);
54
+ }
55
+ function preferredPageIndex(entries, preferJavaScript) {
56
+ const files = new Set(entries.filter((entry) => entry.kind === 'file').map((entry) => entry.name));
57
+ const extensions = preferJavaScript
58
+ ? ['js', 'mjs', 'cjs', 'ts', 'tsx']
59
+ : ['ts', 'tsx', 'js', 'mjs', 'cjs'];
60
+ return extensions.map((extension) => `index.${extension}`).find((name) => files.has(name));
45
61
  }
46
62
  export { pageFeature };
47
63
  export default pageFeature;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/page",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Convention-based Page capability Feature for Zhin.js plugins",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -17,9 +17,9 @@
17
17
  "src"
18
18
  ],
19
19
  "dependencies": {
20
+ "@zhin.js/plugin-runtime": "1.1.11",
20
21
  "@zhin.js/console-contract": "1.1.0",
21
- "@zhin.js/feature-kit": "1.1.0",
22
- "@zhin.js/plugin-runtime": "1.1.9"
22
+ "@zhin.js/feature-kit": "1.1.2"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^26.1.2",
package/src/provider.ts CHANGED
@@ -1,6 +1,9 @@
1
- import { join, parse } from 'node:path';
1
+ import { join, sep } from 'node:path';
2
2
  import { featureId } from '@zhin.js/plugin-runtime';
3
- import { defineFeatureProvider, type SourceConvention } from '@zhin.js/feature-kit';
3
+ import {
4
+ defineFeatureProvider,
5
+ type SourceConvention,
6
+ } from '@zhin.js/feature-kit';
4
7
  import { parsePageArtifact } from './definition.js';
5
8
  import { PageIndex } from './page-index.js';
6
9
 
@@ -13,10 +16,18 @@ const pageFiles: SourceConvention = {
13
16
  const entries = [...await context.host.list(directory)]
14
17
  .sort((left, right) => left.name.localeCompare(right.name));
15
18
  for (const entry of entries) {
16
- if (entry.kind !== 'file' || !isPageFile(entry.name)) continue;
19
+ if (entry.kind !== 'directory' || !isPageName(entry.name)
20
+ || entry.name === 'nav' || entry.name === 'footer') continue;
21
+ const moduleDirectory = join(directory, entry.name);
22
+ const moduleEntries = await context.host.list(moduleDirectory);
23
+ const index = preferredPageIndex(moduleEntries, context.packageRoot.split(sep).includes('node_modules'));
24
+ if (!index) continue;
17
25
  yield {
18
- localName: parse(entry.name).name,
19
- source: join(directory, entry.name),
26
+ localName: entry.name,
27
+ source: join(moduleDirectory, index),
28
+ relatedSources: moduleEntries
29
+ .filter((candidate) => candidate.kind === 'file' && candidate.name !== index)
30
+ .map((candidate) => join(moduleDirectory, candidate.name)),
20
31
  target: 'client',
21
32
  };
22
33
  }
@@ -42,8 +53,19 @@ const pageFeature = defineFeatureProvider({
42
53
  },
43
54
  });
44
55
 
45
- function isPageFile(value: string): boolean {
46
- return /^[a-z0-9][a-z0-9-]*\.tsx?$/u.test(value);
56
+ function isPageName(value: string): boolean {
57
+ return /^[a-z0-9][a-z0-9-]*$/u.test(value);
58
+ }
59
+
60
+ function preferredPageIndex(
61
+ entries: readonly { readonly name: string; readonly kind: 'file' | 'directory' }[],
62
+ preferJavaScript: boolean,
63
+ ): string | undefined {
64
+ const files = new Set(entries.filter((entry) => entry.kind === 'file').map((entry) => entry.name));
65
+ const extensions = preferJavaScript
66
+ ? ['js', 'mjs', 'cjs', 'ts', 'tsx']
67
+ : ['ts', 'tsx', 'js', 'mjs', 'cjs'];
68
+ return extensions.map((extension) => `index.${extension}`).find((name) => files.has(name));
47
69
  }
48
70
 
49
71
  export { pageFeature };