@blocklet/pages-kit-block-studio 0.6.0 โ†’ 0.6.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.
@@ -1,188 +1,235 @@
1
- import fs from 'fs';
2
- import path from 'path';
1
+ /**
2
+ * Vite Plugin: Component Code Splitter
3
+ *
4
+ * ๐Ÿš€ WORKFLOW OVERVIEW:
5
+ *
6
+ * โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
7
+ * โ”‚ PLUGIN 1: CODE SPLITTER โ”‚
8
+ * โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
9
+ *
10
+ * ๐Ÿ“‹ Phase 1: Config Stage
11
+ * โ”œโ”€โ”€ Analyze entry files to find all exports
12
+ * โ”œโ”€โ”€ Detect: default, EditComponent, PropertiesSchema, GetServerSideProps
13
+ * โ””โ”€โ”€ Create separate entry points for each export:
14
+ * โ€ข Component โ†’ Component?exportName=default&target=browser
15
+ * โ€ข EditComponent โ†’ Component_EditComponent?exportName=EditComponent&target=browser
16
+ * โ€ข GetServerSideProps โ†’ Component_GetServerSideProps?exportName=GetServerSideProps&target=node
17
+ *
18
+ * ๐Ÿ”„ Phase 2: Resolve & Load Stage
19
+ * โ”œโ”€โ”€ resolveId: Identify virtual modules with ?exportName= params
20
+ * โ””โ”€โ”€ load: Transform code and split exports
21
+ * โ”œโ”€โ”€ Use esbuild for fast transformation
22
+ * โ”œโ”€โ”€ Apply tree-shaking to remove unused code
23
+ * โ””โ”€โ”€ Filter to keep only specified exports
24
+ *
25
+ * โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
26
+ * โ”‚ PLUGIN 2: POST-BUILD PROCESSING โ”‚
27
+ * โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
28
+ *
29
+ * ๐Ÿ”ง Phase 3: Post-Build Optimization
30
+ * โ”œโ”€โ”€ Scan generated files and classify by target:
31
+ * โ”‚ โ”œโ”€โ”€ Node.js Target: GetServerSideProps functions
32
+ * โ”‚ โ””โ”€โ”€ Browser Target: Components, EditComponents, Schemas
33
+ * โ”œโ”€โ”€ Node.js files: Re-bundle with esbuild (platform: node, format: cjs)
34
+ * โ”œโ”€โ”€ Browser files: Transpile with TypeScript + builtin module transformer
35
+ * โ””โ”€โ”€ Generate dependency mapping (chunks-map.json)
36
+ *
37
+ * ๐Ÿ’ก WHY THIS DESIGN:
38
+ * โ€ข Reduces bundle size by splitting unused exports
39
+ * โ€ข Optimizes for different runtime environments (browser vs node)
40
+ * โ€ข Enables selective loading in Component Studio
41
+ * โ€ข Maintains clean separation between edit-time and runtime code
42
+ */
43
+ import { BuiltinModules } from '@blocklet/pages-kit/utils/builtin';
44
+ import { createBuiltinModuleTransformer } from '@blocklet/pages-kit/utils/typescript/builtin-module-transformer';
45
+ // @ts-ignore
46
+ import { analyzeFileChunks } from '@blocklet/pages-kit/utils/typescript/chunks-analyzer-transformer';
47
+ import * as esbuild from 'esbuild';
48
+ import fs, { readFileSync, existsSync, mkdirSync, writeFileSync, readdirSync, statSync } from 'fs';
49
+ import pLimit from 'p-limit';
50
+ import path, { basename } from 'path';
3
51
  import ts from 'typescript';
4
52
  import { EDIT_COMPONENT_FILE_NAME_REGEX } from '../constants';
5
- import { EDIT_COMPONENT_NAME, getEditComponentBlockName, logger } from '../utils/helper';
53
+ import { EDIT_COMPONENT_NAME, getEditComponentBlockName, logger, PROPERTIES_SCHEMA_NAME, AIGNE_OUTPUT_VALUE_SCHEMA_NAME, GET_SERVER_SIDE_PROPS_NAME, getPropertiesSchemaBlockName, getAigneOutputValueSchemaBlockName, getGetServerSidePropsBlockName, } from '../utils/helper';
54
+ const minify = true;
55
+ /**
56
+ * ๅฏผๅ‡บ้…็ฝฎ
57
+ */
58
+ const EXPORT_CONFIGS = [
59
+ {
60
+ exportName: 'default',
61
+ getBlockName: (key) => `${key}`,
62
+ description: 'Default',
63
+ supportSeparateFile: false,
64
+ target: 'browser',
65
+ external: [],
66
+ },
67
+ // {
68
+ // exportName: 'default',
69
+ // getBlockName: (key) => `${key}(default)`,
70
+ // description: 'Default CJS',
71
+ // supportSeparateFile: false,
72
+ // target: 'node',
73
+ // external: [],
74
+ // },
75
+ {
76
+ exportName: EDIT_COMPONENT_NAME,
77
+ getBlockName: getEditComponentBlockName,
78
+ description: 'EditComponent',
79
+ supportSeparateFile: true, // ๆ”ฏๆŒ็‹ฌ็ซ‹็š„ @edit-component ๆ–‡ไปถ
80
+ target: 'browser', // ๅ‰็ซฏ็ป„ไปถ
81
+ external: [],
82
+ },
83
+ {
84
+ exportName: PROPERTIES_SCHEMA_NAME,
85
+ getBlockName: getPropertiesSchemaBlockName,
86
+ description: 'PropertiesSchema',
87
+ supportSeparateFile: false,
88
+ target: 'browser', // ๅ‰็ซฏ schema
89
+ external: [],
90
+ },
91
+ {
92
+ exportName: AIGNE_OUTPUT_VALUE_SCHEMA_NAME,
93
+ getBlockName: getAigneOutputValueSchemaBlockName,
94
+ description: 'AigneOutputValueSchema',
95
+ supportSeparateFile: false,
96
+ target: 'browser', // ๅ‰็ซฏ schema
97
+ external: [],
98
+ },
99
+ {
100
+ exportName: GET_SERVER_SIDE_PROPS_NAME,
101
+ getBlockName: getGetServerSidePropsBlockName,
102
+ description: 'GetServerSideProps',
103
+ supportSeparateFile: false,
104
+ target: 'node', // ๆœๅŠก็ซฏๅ‡ฝๆ•ฐ
105
+ format: 'esm', // esm ๆ ผๅผ
106
+ // ๆŽ’้™ค็š„ไพ่ต–, ้œ€่ฆ่ทŸๅŽ็ซฏไฟๆŒไธ€่‡ด
107
+ external: [...Object.keys(BuiltinModules)],
108
+ },
109
+ ];
6
110
  /**
7
- * ๅˆ†ๆž็ป„ไปถๆ–‡ไปถ็ป“ๆž„
8
- * @param sourceFile TypeScriptๆบๆ–‡ไปถ
111
+ * ๅˆ†ๆž็ป„ไปถๆ–‡ไปถไธญ็š„ๆ‰€ๆœ‰ๅฏผๅ‡บ
9
112
  */
10
- function analyzeComponent(sourceFile) {
11
- const result = {};
12
- // ่ฎฟ้—ฎAST่Š‚็‚น
113
+ function analyzeAllExports(sourceFile) {
114
+ const result = {
115
+ namedExports: new Map(),
116
+ exportDeclarations: [],
117
+ };
13
118
  function visit(node) {
14
- // ๆ‰พๅˆฐ้ป˜่ฎคๅฏผๅ‡บ
15
- if (ts.isExportAssignment(node)) {
16
- result.defaultExport = {
17
- pos: node.pos,
18
- end: node.end,
19
- };
119
+ // ้ป˜่ฎคๅฏผๅ‡บ - export default xxx
120
+ if (ts.isExportAssignment(node) && !node.isExportEquals) {
121
+ result.defaultExport = { pos: node.pos, end: node.end };
20
122
  }
21
- // ๆฃ€ๆŸฅๅ‡ฝๆ•ฐๅฃฐๆ˜Ž็š„้ป˜่ฎคๅฏผๅ‡บ
123
+ // ๅ‡ฝๆ•ฐๅฃฐๆ˜Ž็š„้ป˜่ฎคๅฏผๅ‡บ - export default function
22
124
  if (ts.isFunctionDeclaration(node) && node.modifiers) {
23
125
  const isExport = node.modifiers.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
24
126
  const isDefault = node.modifiers.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword);
25
127
  if (isExport && isDefault) {
26
- result.defaultExport = {
27
- pos: node.pos,
28
- end: node.end,
29
- };
128
+ result.defaultExport = { pos: node.pos, end: node.end };
30
129
  }
31
- // ๆฃ€ๆŸฅๅฏผๅ‡บ็š„ EditComponent ๅ‡ฝๆ•ฐๅฃฐๆ˜Ž
32
- if (isExport && !isDefault && node.name && node.name.text === EDIT_COMPONENT_NAME) {
33
- result.editComponent = {
34
- pos: node.pos,
35
- end: node.end,
36
- };
130
+ else if (isExport && !isDefault && node.name) {
131
+ // ๅ‘ฝๅๅฏผๅ‡บ็š„ๅ‡ฝๆ•ฐ - export function xxx
132
+ result.namedExports.set(node.name.text, { pos: node.pos, end: node.end });
37
133
  }
38
134
  }
39
- // ๆฃ€ๆŸฅ็ฑปๅฃฐๆ˜Ž็š„ๅฏผๅ‡บ
135
+ // ็ฑปๅฃฐๆ˜Ž็š„ๅฏผๅ‡บ
40
136
  if (ts.isClassDeclaration(node) && node.modifiers) {
41
137
  const isExport = node.modifiers.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
42
- // ๆฃ€ๆŸฅๅฏผๅ‡บ็š„ EditComponent ็ฑปๅฃฐๆ˜Ž
43
- if (isExport && node.name && node.name.text === EDIT_COMPONENT_NAME) {
44
- result.editComponent = {
45
- pos: node.pos,
46
- end: node.end,
47
- };
138
+ if (isExport && node.name) {
139
+ result.namedExports.set(node.name.text, { pos: node.pos, end: node.end });
48
140
  }
49
141
  }
50
- // ๆ‰พๅˆฐๅ‘ฝๅๅฏผๅ‡บ - EditComponent
142
+ // ๅ˜้‡ๅฃฐๆ˜Ž็š„ๅฏผๅ‡บ - export const xxx
51
143
  if (ts.isVariableStatement(node) && node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {
52
144
  const { declarations } = node.declarationList;
53
145
  for (const decl of declarations) {
54
- if (ts.isIdentifier(decl.name) && decl.name.text === EDIT_COMPONENT_NAME) {
55
- result.editComponent = {
56
- pos: node.pos,
57
- end: node.end,
58
- };
59
- break;
60
- }
61
- }
62
- }
63
- // ๆฃ€ๆŸฅๅฏผๅ‡บๅฃฐๆ˜Ž
64
- if (ts.isExportDeclaration(node) && node.exportClause) {
65
- if (ts.isNamedExports(node.exportClause)) {
66
- // ๆฃ€ๆŸฅๆ™ฎ้€šๅฏผๅ‡บ export { EditComponent }
67
- const hasEditComponent = node.exportClause.elements.some((element) => ts.isExportSpecifier(element) && element.name.text === EDIT_COMPONENT_NAME);
68
- // ๆฃ€ๆŸฅ้‡ๅ‘ฝๅๅฏผๅ‡บ export { SomeComponent as EditComponent }
69
- const hasRenamedEditComponent = node.exportClause.elements.some((element) => ts.isExportSpecifier(element) && element.propertyName && element.name.text === EDIT_COMPONENT_NAME);
70
- if (hasEditComponent || hasRenamedEditComponent) {
71
- result.editComponent = {
72
- pos: node.pos,
73
- end: node.end,
74
- };
146
+ if (ts.isIdentifier(decl.name)) {
147
+ result.namedExports.set(decl.name.text, { pos: node.pos, end: node.end });
75
148
  }
76
149
  }
77
150
  }
78
- // ๅค„็†ๅฏผๅ‡บ็š„็ฎญๅคดๅ‡ฝๆ•ฐ/ๅ‡ฝๆ•ฐ่กจ่พพๅผ
79
- if (ts.isVariableStatement(node)) {
80
- const { declarations } = node.declarationList;
81
- for (const decl of declarations) {
82
- if (ts.isIdentifier(decl.name) && decl.name.text === EDIT_COMPONENT_NAME) {
83
- if (node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {
84
- result.editComponent = {
85
- pos: node.pos,
86
- end: node.end,
87
- };
88
- break;
151
+ // ๅฏผๅ‡บๅฃฐๆ˜Ž - export { xxx, yyy }
152
+ if (ts.isExportDeclaration(node)) {
153
+ result.exportDeclarations.push({ pos: node.pos, end: node.end });
154
+ if (node.exportClause && ts.isNamedExports(node.exportClause)) {
155
+ // ่ฎฐๅฝ•่ฟ™ไธชๅฏผๅ‡บๅฃฐๆ˜ŽๅŒ…ๅซ็š„ๆ‰€ๆœ‰ๅฏผๅ‡บๅ
156
+ node.exportClause.elements.forEach((element) => {
157
+ if (ts.isExportSpecifier(element)) {
158
+ const exportName = element.propertyName ? element.name.text : element.name.text;
159
+ result.namedExports.set(exportName, { pos: node.pos, end: node.end });
89
160
  }
90
- }
161
+ });
91
162
  }
92
163
  }
93
- // ้€’ๅฝ’ๅค„็†ๆ‰€ๆœ‰ๅญ่Š‚็‚น
94
164
  ts.forEachChild(node, visit);
95
165
  }
96
- // ๅผ€ๅง‹้ๅކ
97
166
  visit(sourceFile);
98
167
  return result;
99
168
  }
100
169
  /**
101
- * ่ฝฌๆขไปฃ็ ๏ผŒไฝฟ็”จTypeScript็ผ–่ฏ‘ๅ™จAPI
170
+ * ่งฃๆž exportName ๅ‚ๆ•ฐ
102
171
  */
103
- function transformCode(code, renderType) {
104
- // ๅˆ›ๅปบๆบๆ–‡ไปถ
105
- const sourceFile = ts.createSourceFile('temp.tsx', code, ts.ScriptTarget.Latest, true);
106
- // ๅˆ†ๆžๆบไปฃ็ ็ป“ๆž„
107
- const analysis = analyzeComponent(sourceFile);
108
- if (renderType === 'view') {
109
- // ็งป้™ค EditComponent
110
- if (analysis.editComponent) {
111
- // ๆ นๆฎไฝ็ฝฎๆ›ฟๆขไธบ็ฉบๅ†…ๅฎน
112
- const beforeEdit = code.substring(0, analysis.editComponent.pos);
113
- const afterEdit = code.substring(analysis.editComponent.end);
114
- return beforeEdit + afterEdit;
115
- }
116
- }
117
- else if (renderType === 'setting') {
118
- // ็งป้™คๆˆ–ๆ›ฟๆข้ป˜่ฎคๅฏผๅ‡บ๏ผŒ็›ดๆŽฅๅฏผๅ‡บEditComponent
119
- if (analysis.defaultExport && analysis.editComponent) {
120
- // ็ผ–่พ‘ๆจกๅผ๏ผŒไฟๆŒๅ‘ฝๅๅฏผๅ‡บ๏ผŒไฝ†่ฎฉEditComponentๅŒๆ—ถไนŸไฝœไธบ้ป˜่ฎคๅฏผๅ‡บ
121
- const defaultExport = `
122
- // Export EditComponent as both named export and default
123
- export { ${EDIT_COMPONENT_NAME} as default };
124
- `;
125
- const beforeDefault = code.substring(0, analysis.defaultExport.pos);
126
- const afterDefault = code.substring(analysis.defaultExport.end);
127
- // ๆ›ฟๆข้ป˜่ฎคๅฏผๅ‡บไธบEditComponent
128
- return beforeDefault + defaultExport + afterDefault;
129
- }
130
- }
131
- // ๅฆ‚ๆžœๆฒกๆœ‰่ฟ›่กŒไฟฎๆ”น๏ผŒ่ฟ”ๅ›žๅŽŸๅง‹ไปฃ็ 
132
- return code;
172
+ function parseExportNames(exportNameParam) {
173
+ return exportNameParam
174
+ .split(',')
175
+ .map((name) => name.trim())
176
+ .filter(Boolean);
133
177
  }
134
178
  /**
135
- * ่ฟ›ไธ€ๆญฅๅขžๅผบ็‰ˆๆœฌ๏ผš้€š่ฟ‡ๅฎŒๆ•ด็š„AST่ฝฌๆข
179
+ * ่ฝฌๆขไปฃ็ ๏ผŒๅชไฟ็•™ๆŒ‡ๅฎš็š„ๅฏผๅ‡บ
136
180
  */
137
- function transformCodeWithPrinter(code, renderType) {
181
+ function transformCodeWithExports(code, exportNames) {
138
182
  try {
139
- // ๅˆ›ๅปบๆบๆ–‡ไปถ
140
183
  const sourceFile = ts.createSourceFile('temp.tsx', code, ts.ScriptTarget.Latest, true);
141
- // ๅˆ›ๅปบ่ฝฌๆขๅ™จไธŠไธ‹ๆ–‡
184
+ // ๆ ‡ๅ‡†ๅŒ–ๅฏผๅ‡บๅ็งฐ๏ผˆdefault ๅค„็†๏ผ‰
185
+ const normalizedExportNames = new Set(exportNames.map((name) => (name === 'default' ? 'default' : name)));
142
186
  const transformerFactory = (context) => {
143
187
  return (sourceFile) => {
144
- // ่ฎฟ้—ฎๅนถ่ฝฌๆข่Š‚็‚น
145
188
  const visitor = (node) => {
146
- // viewๆจกๅผ๏ผš็งป้™คEditComponent็›ธๅ…ณไปฃ็ 
147
- if (renderType === 'view') {
148
- // ็งป้™ค export const EditComponent ๅฃฐๆ˜Ž
149
- if (ts.isVariableStatement(node) && node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {
150
- const { declarations } = node.declarationList;
151
- if (declarations.some((d) => ts.isIdentifier(d.name) && d.name.text === EDIT_COMPONENT_NAME)) {
152
- return ts.factory.createEmptyStatement();
153
- }
189
+ // ๅค„็†้ป˜่ฎคๅฏผๅ‡บ
190
+ if (ts.isExportAssignment(node) && !node.isExportEquals) {
191
+ return normalizedExportNames.has('default') ? node : ts.factory.createEmptyStatement();
192
+ }
193
+ // ๅค„็†ๅ‡ฝๆ•ฐ/็ฑป็š„้ป˜่ฎคๅฏผๅ‡บ
194
+ if (ts.isFunctionDeclaration(node) && node.modifiers) {
195
+ const isExport = node.modifiers.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
196
+ const isDefault = node.modifiers.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword);
197
+ if (isExport && isDefault) {
198
+ return normalizedExportNames.has('default') ? node : ts.factory.createEmptyStatement();
154
199
  }
155
- // ็งป้™ค export { EditComponent } ๅฝขๅผ็š„ๅฏผๅ‡บ
156
- if (ts.isExportDeclaration(node) && node.exportClause && ts.isNamedExports(node.exportClause)) {
157
- const { elements } = node.exportClause;
158
- if (elements.some((e) => ts.isExportSpecifier(e) && e.name.text === EDIT_COMPONENT_NAME)) {
159
- // ๅฆ‚ๆžœๅชๆœ‰EditComponentไธ€ไธชๅฏผๅ‡บ๏ผŒๅฐฑๅฎŒๅ…จ็งป้™ค
160
- if (elements.length === 1) {
161
- return ts.factory.createEmptyStatement();
162
- }
163
- // ๅฆๅˆ™ๅˆ›ๅปบๆ–ฐ็š„ๅฏผๅ‡บๅฃฐๆ˜Ž๏ผŒไฝ†ไธๅŒ…ๅซEditComponent
164
- const newElements = elements.filter((e) => !(ts.isExportSpecifier(e) && e.name.text === EDIT_COMPONENT_NAME));
165
- return ts.factory.createExportDeclaration(node.modifiers, node.isTypeOnly, ts.factory.createNamedExports(newElements), node.moduleSpecifier);
166
- }
200
+ if (isExport && !isDefault && node.name) {
201
+ return normalizedExportNames.has(node.name.text) ? node : ts.factory.createEmptyStatement();
167
202
  }
168
203
  }
169
- // settingๆจกๅผ๏ผš็›ดๆŽฅๅฏผๅ‡บEditComponent
170
- if (renderType === 'setting') {
171
- // ๆ‰พๅˆฐ้ป˜่ฎคๅฏผๅ‡บ่Š‚็‚น่ฟ›่กŒๆ›ฟๆข
172
- if (ts.isExportAssignment(node) && node.isExportEquals === false) {
173
- // ๆ›ฟๆข export default xxx ๅฝขๅผไธบ export { EditComponent as default }
174
- return ts.factory.createExportDeclaration(undefined, false, ts.factory.createNamedExports([
175
- ts.factory.createExportSpecifier(false, ts.factory.createIdentifier(EDIT_COMPONENT_NAME), ts.factory.createIdentifier('default')),
176
- ]));
204
+ // ๅค„็†็ฑปๅฃฐๆ˜Ž็š„ๅฏผๅ‡บ
205
+ if (ts.isClassDeclaration(node) && node.modifiers) {
206
+ const isExport = node.modifiers.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
207
+ if (isExport && node.name) {
208
+ return normalizedExportNames.has(node.name.text) ? node : ts.factory.createEmptyStatement();
177
209
  }
178
- // ๅค„็† export default function Xxx() {} ๅฝขๅผ
179
- if (ts.isFunctionDeclaration(node) &&
180
- node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) &&
181
- node.modifiers?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword)) {
182
- // ๅˆ›ๅปบๆ–ฐ็š„้ป˜่ฎคๅฏผๅ‡บ๏ผŒๅฏผๅ‡บEditComponentไฝœไธบdefault
183
- return ts.factory.createExportDeclaration(undefined, false, ts.factory.createNamedExports([
184
- ts.factory.createExportSpecifier(false, ts.factory.createIdentifier(EDIT_COMPONENT_NAME), ts.factory.createIdentifier('default')),
185
- ]));
210
+ }
211
+ // ๅค„็†ๅ˜้‡ๅฃฐๆ˜Ž็š„ๅฏผๅ‡บ
212
+ if (ts.isVariableStatement(node) && node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {
213
+ const { declarations } = node.declarationList;
214
+ const shouldKeep = declarations.some((decl) => ts.isIdentifier(decl.name) && normalizedExportNames.has(decl.name.text));
215
+ return shouldKeep ? node : ts.factory.createEmptyStatement();
216
+ }
217
+ // ๅค„็†ๅฏผๅ‡บๅฃฐๆ˜Ž - export { xxx, yyy }
218
+ if (ts.isExportDeclaration(node) && node.exportClause && ts.isNamedExports(node.exportClause)) {
219
+ const { elements } = node.exportClause;
220
+ const keptElements = elements.filter((element) => {
221
+ if (ts.isExportSpecifier(element)) {
222
+ const exportName = element.propertyName ? element.name.text : element.name.text;
223
+ return normalizedExportNames.has(exportName);
224
+ }
225
+ return false;
226
+ });
227
+ if (keptElements.length === 0) {
228
+ return ts.factory.createEmptyStatement();
229
+ }
230
+ if (keptElements.length < elements.length) {
231
+ // ๅˆ›ๅปบๆ–ฐ็š„ๅฏผๅ‡บๅฃฐๆ˜Ž๏ผŒๅชๅŒ…ๅซ้œ€่ฆไฟ็•™็š„ๅฏผๅ‡บ
232
+ return ts.factory.createExportDeclaration(node.modifiers, node.isTypeOnly, ts.factory.createNamedExports(keptElements), node.moduleSpecifier);
186
233
  }
187
234
  }
188
235
  return ts.visitEachChild(node, visitor, context);
@@ -195,10 +242,9 @@ function transformCodeWithPrinter(code, renderType) {
195
242
  const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
196
243
  if (result.transformed.length > 0) {
197
244
  const transformedSourceFile = result.transformed[0];
198
- // TypeScript ็ฑปๅž‹ไฟ่ฏ
199
245
  if (transformedSourceFile) {
200
246
  const transformedCode = printer.printFile(transformedSourceFile);
201
- result.dispose(); // ๆธ…็†่ต„ๆบ
247
+ result.dispose();
202
248
  return transformedCode;
203
249
  }
204
250
  }
@@ -206,40 +252,68 @@ function transformCodeWithPrinter(code, renderType) {
206
252
  return code;
207
253
  }
208
254
  catch (error) {
209
- logger.error('Error transforming with printer:', error);
210
- // ๅฆ‚ๆžœ้ซ˜็บง่ฝฌๆขๅคฑ่ดฅ๏ผŒๅ›ž้€€ๅˆฐ็ฎ€ๅ•่ฝฌๆข
211
- return transformCode(code, renderType);
255
+ logger.error('Error transforming with exports:', error);
256
+ return code;
212
257
  }
213
258
  }
214
259
  /**
215
- * ๆฃ€ๆŸฅๆ–‡ไปถๆ˜ฏๅฆๅŒ…ๅซEditComponentๅฏผๅ‡บ
260
+ * ็ฎ€ๅ•็š„ไปฃ็ ่ฝฌๆข๏ผŒ้€š่ฟ‡ๅญ—็ฌฆไธฒๆ›ฟๆข๏ผˆfallback๏ผ‰
216
261
  */
217
- function hasEditComponentExportInEntryFile(filePath) {
262
+ function transformCodeSimple(code, exportNames) {
263
+ const sourceFile = ts.createSourceFile('temp.tsx', code, ts.ScriptTarget.Latest, true);
264
+ const analysis = analyzeAllExports(sourceFile);
265
+ const normalizedExportNames = new Set(exportNames.map((name) => (name === 'default' ? 'default' : name)));
266
+ let result = code;
267
+ const toRemove = [];
268
+ // ๆ”ถ้›†้œ€่ฆ็งป้™ค็š„่Š‚็‚น
269
+ if (analysis.defaultExport && !normalizedExportNames.has('default')) {
270
+ toRemove.push(analysis.defaultExport);
271
+ }
272
+ analysis.namedExports.forEach((range, name) => {
273
+ if (!normalizedExportNames.has(name)) {
274
+ toRemove.push(range);
275
+ }
276
+ });
277
+ // ๆŒ‰ไฝ็ฝฎๅ€’ๅบๆŽ’ๅˆ—๏ผŒ้ฟๅ…ไฝ็ฝฎๅ็งป
278
+ toRemove.sort((a, b) => b.pos - a.pos);
279
+ // ็งป้™คไธ้œ€่ฆ็š„ๅฏผๅ‡บ
280
+ for (const range of toRemove) {
281
+ const before = result.substring(0, range.pos);
282
+ const after = result.substring(range.end);
283
+ result = before + after;
284
+ }
285
+ return result;
286
+ }
287
+ /**
288
+ * ่Žทๅ–ๆ–‡ไปถไธญ็š„ๆ‰€ๆœ‰ๅฏผๅ‡บๅ็งฐ
289
+ */
290
+ function getFileExports(filePath) {
218
291
  try {
219
292
  if (!fs.existsSync(filePath)) {
220
- return false;
293
+ return [];
221
294
  }
222
- // ่ฏปๅ–ๆ–‡ไปถๅ†…ๅฎน
223
295
  const content = fs.readFileSync(filePath, 'utf-8');
224
- // ๅˆ›ๅปบๆบๆ–‡ไปถ
225
296
  const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
226
- // ไฝฟ็”จๅทฒๆœ‰็š„analyzeComponentๅ‡ฝๆ•ฐๅˆ†ๆž
227
- const analysis = analyzeComponent(sourceFile);
228
- // ๅฆ‚ๆžœๅญ˜ๅœจEditComponentๅฏผๅ‡บ๏ผŒๅˆ™่ฟ”ๅ›žtrue
229
- return !!analysis.editComponent;
297
+ const analysis = analyzeAllExports(sourceFile);
298
+ const exports = [];
299
+ if (analysis.defaultExport) {
300
+ exports.push('default');
301
+ }
302
+ exports.push(...Array.from(analysis.namedExports.keys()));
303
+ return exports;
230
304
  }
231
305
  catch (error) {
232
- logger.warn(`Error checking EditComponent in ${filePath}:`, error);
233
- return false;
306
+ logger.warn(`Error analyzing exports in ${filePath}:`, error);
307
+ return [];
234
308
  }
235
309
  }
236
- // ๆฃ€ๆŸฅ็›ฎๅฝ•ไธญๆ˜ฏๅฆๅญ˜ๅœจ@edit-componentๆ–‡ไปถ
310
+ /**
311
+ * ๆฃ€ๆŸฅ็›ฎๅฝ•ไธญๆ˜ฏๅฆๅญ˜ๅœจ@edit-componentๆ–‡ไปถ
312
+ */
237
313
  function findEditComponentFileInDir(filePath) {
238
314
  const dirPath = path.dirname(filePath);
239
- // ่Žทๅ–็›ฎๅฝ•ไธญ็š„ๆ‰€ๆœ‰ๆ–‡ไปถ
240
315
  try {
241
316
  const files = fs.readdirSync(dirPath);
242
- // ๆฃ€ๆŸฅๆ˜ฏๅฆๆœ‰ๅŒน้…็š„ๆ–‡ไปถ
243
317
  const file = files.find((file) => EDIT_COMPONENT_FILE_NAME_REGEX.test(file));
244
318
  if (file) {
245
319
  return path.join(dirPath, file);
@@ -252,105 +326,346 @@ function findEditComponentFileInDir(filePath) {
252
326
  }
253
327
  }
254
328
  /**
255
- * Vite ๆ’ไปถ๏ผš็ป„ไปถไปฃ็ ๆ‹†ๅˆ†ๅ™จ
256
- * ็”จไบŽ่‡ชๅŠจๅฐ†็ป„ไปถๆ–‡ไปถๆ‹†ๅˆ†ไธบ่ง†ๅ›พ็ป„ไปถๅ’Œ็ผ–่พ‘็ป„ไปถ
329
+ * ๐ŸŽฏ MAIN EXPORT: Vite Plugin Factory
330
+ * Creates two plugins that work together to split and optimize component code
257
331
  */
258
- export function vitePluginCodeSplitter() {
259
- return {
260
- name: 'vite-plugin-code-splitter',
261
- config(config) {
262
- // ๅชๆœ‰ๅœจๆž„ๅปบๆจกๅผไธ”lib้…็ฝฎๅญ˜ๅœจๆ—ถๅค„็†
263
- if (config.build && config.build.lib && typeof config.build.lib === 'object') {
264
- const libConfig = config.build.lib;
265
- if (libConfig.entry && typeof libConfig.entry === 'object' && !Array.isArray(libConfig.entry)) {
266
- // ๅˆ›ๅปบๆ–ฐ็š„ๅ…ฅๅฃๅˆ—่กจ
267
- const newEntry = {};
268
- Object.entries(libConfig.entry).forEach(([key, filePath]) => {
269
- // ๅฏนไบŽๆ™ฎ้€šๆจกๅ—่ทฏๅพ„๏ผŒๆทปๅŠ URLๅ‚ๆ•ฐ
270
- if (typeof filePath === 'string') {
271
- let editComponentFilePath = null;
272
- // ๆฃ€ๆต‹ๆ–‡ไปถๆ˜ฏๅฆๅŒ…ๅซEditComponent
273
- let hasEditComponent = false;
274
- try {
275
- // ไฝฟ็”จTypeScriptๅˆ†ๆžๆ–‡ไปถ
276
- hasEditComponent = hasEditComponentExportInEntryFile(filePath);
277
- // ่ฎฐๅฝ•ๆฃ€ๆต‹ๅˆฐ็š„ๆ–‡ไปถ
278
- const fileName = path.basename(filePath);
279
- if (hasEditComponent) {
280
- logger.info(`Found EditComponent in ${fileName}`);
281
- editComponentFilePath = filePath;
332
+ export function vitePluginCodeSplitter(options) {
333
+ const formats = options?.formats || ['es', 'cjs'];
334
+ const transpileBuiltinModule = options?.transpileBuiltinModule ?? true;
335
+ const skipBundleNodeTarget = options?.skipBundleNodeTarget ?? false;
336
+ return [
337
+ // ๐Ÿ”ง PLUGIN 1: CODE SPLITTER & TRANSFORMER
338
+ {
339
+ name: 'vite-plugin-code-splitter',
340
+ /**
341
+ * ๐Ÿ“‹ PHASE 1: CONFIG STAGE
342
+ * Analyzes entry files and creates separate entry points for each export type
343
+ */
344
+ config(config) {
345
+ if (config.build && config.build.lib && typeof config.build.lib === 'object') {
346
+ const libConfig = config.build.lib;
347
+ if (libConfig.entry && typeof libConfig.entry === 'object' && !Array.isArray(libConfig.entry)) {
348
+ const newEntry = {};
349
+ Object.entries(libConfig.entry).forEach(([key, filePath]) => {
350
+ if (typeof filePath === 'string') {
351
+ try {
352
+ // ๅˆ†ๆžๆ–‡ไปถไธญ็š„ๆ‰€ๆœ‰ๅฏผๅ‡บ
353
+ const exports = getFileExports(filePath);
354
+ const fileName = path.basename(filePath);
355
+ logger.info(`Found exports in ${fileName}: ${exports.join(', ')}`);
356
+ // ๅค„็†ๆ‰€ๆœ‰้…็ฝฎ็š„ๅฏผๅ‡บ็ฑปๅž‹
357
+ EXPORT_CONFIGS.forEach(({ exportName, getBlockName, description, supportSeparateFile, target }) => {
358
+ if (exports.includes(exportName)) {
359
+ newEntry[getBlockName(key)] = `${filePath}?exportName=${exportName}&target=${target}`;
360
+ logger.debug(`Added ${description} entry for ${fileName} (target: ${target})`);
361
+ }
362
+ else if (supportSeparateFile && exportName === EDIT_COMPONENT_NAME) {
363
+ // ๆฃ€ๆŸฅๆ˜ฏๅฆๆœ‰็‹ฌ็ซ‹็š„ @edit-component ๆ–‡ไปถ
364
+ const editComponentFile = findEditComponentFileInDir(filePath);
365
+ if (editComponentFile) {
366
+ const editExports = getFileExports(editComponentFile);
367
+ if (editExports.includes(EDIT_COMPONENT_NAME)) {
368
+ newEntry[getEditComponentBlockName(key)] =
369
+ `${editComponentFile}?exportName=${EDIT_COMPONENT_NAME}&target=${target}`;
370
+ logger.info(`Found separate @edit-component file for ${path.basename(editComponentFile)} (target: ${target})`);
371
+ }
372
+ }
373
+ }
374
+ });
282
375
  }
283
- else {
284
- logger.debug(`No EditComponent found in ${fileName}`);
285
- editComponentFilePath = findEditComponentFileInDir(filePath);
286
- // ๆฃ€ๆŸฅๆ˜ฏๅฆๅญ˜ๅœจ็‹ฌ็ซ‹็š„@edit-componentๆ–‡ไปถ
287
- if (editComponentFilePath) {
288
- hasEditComponent = true;
289
- logger.info(`Found separate @edit-component file for ${editComponentFilePath}`);
290
- }
376
+ catch (err) {
377
+ logger.warn(`Failed to analyze exports in ${filePath}: ${err}`);
378
+ // fallback ๅˆฐๅŽŸๅง‹ๆ–‡ไปถ
379
+ newEntry[key] = filePath;
291
380
  }
292
381
  }
293
- catch (err) {
294
- logger.warn(`Failed to check EditComponent in ${filePath}: ${err}`);
295
- }
296
- // ่ง†ๅ›พ็ป„ไปถๆ€ปๆ˜ฏไฟ็•™
297
- newEntry[key] = `${filePath}?renderType=view`;
298
- // ๅชไธบๅŒ…ๅซEditComponent็š„ๆ–‡ไปถๅˆ›ๅปบ็ผ–่พ‘ๅ…ฅๅฃ
299
- if (hasEditComponent) {
300
- newEntry[getEditComponentBlockName(key)] = `${editComponentFilePath}?renderType=setting`;
301
- }
302
- }
303
- });
304
- // ๆ›ดๆ–ฐ้…็ฝฎ
305
- libConfig.entry = newEntry;
306
- logger.info('Auto-split components enabled, entries updated');
382
+ });
383
+ libConfig.entry = newEntry;
384
+ logger.info('Auto-split components enabled with export-based splitting');
385
+ }
307
386
  }
308
- }
309
- return config;
310
- },
311
- resolveId(id) {
312
- // ๅค„็†ๅธฆๆœ‰ renderType ๅ‚ๆ•ฐ็š„ๆจกๅ— ID
313
- if (id.includes('?renderType=')) {
314
- return id; // ๆ ‡่ฎฐไธบ้œ€่ฆๅค„็†็š„่™šๆ‹Ÿๆจกๅ—
315
- }
316
- return null;
317
- },
318
- load(id) {
319
- if (!id.includes('?renderType=')) {
320
- return null;
321
- }
322
- try {
323
- // ่งฃๆž id๏ผŒๆๅ–ๆ–‡ไปถ่ทฏๅพ„ๅ’ŒๆŸฅ่ฏขๅ‚ๆ•ฐ
324
- const [filePath, query] = id.split('?');
325
- const params = new URLSearchParams(query);
326
- const renderType = params.get('renderType');
327
- if (!filePath || !renderType || (renderType !== 'view' && renderType !== 'setting')) {
328
- return null;
387
+ return config;
388
+ },
389
+ /**
390
+ * ๐Ÿ” PHASE 2A: RESOLVE STAGE
391
+ * Identifies virtual modules with exportName parameters
392
+ */
393
+ resolveId(id) {
394
+ if (id.includes('?exportName=')) {
395
+ return id; // Mark as resolved to trigger load() for this virtual module
329
396
  }
330
- // ็กฎไฟๆ–‡ไปถๅญ˜ๅœจ
331
- if (!fs.existsSync(filePath)) {
332
- logger.error(`File not found: ${filePath}`);
397
+ return null;
398
+ },
399
+ /**
400
+ * ๐Ÿ”„ PHASE 2B: LOAD & TRANSFORM STAGE
401
+ * Loads and transforms code, keeping only specified exports
402
+ */
403
+ async load(id) {
404
+ if (!id.includes('?exportName=')) {
333
405
  return null;
334
406
  }
335
- // ่ฏปๅ–ๆ–‡ไปถๅ†…ๅฎน
336
- const code = fs.readFileSync(filePath, 'utf-8');
337
- // ๆ นๆฎๆธฒๆŸ“็ฑปๅž‹่ฟ›่กŒไปฃ็ ่ฝฌๆข
338
- // logger.info(`Splitting code for ${renderType}: ${filePath}`);
339
407
  try {
340
- // ้ฆ–ๅ…ˆๅฐ่ฏ•ไฝฟ็”จ้ซ˜็บง่ฝฌๆข
341
- return transformCodeWithPrinter(code, renderType);
408
+ const [filePath, query] = id.split('?');
409
+ const params = new URLSearchParams(query);
410
+ const exportNameParam = params.get('exportName');
411
+ const target = params.get('target') || 'browser';
412
+ if (!filePath || !exportNameParam) {
413
+ return null;
414
+ }
415
+ if (!fs.existsSync(filePath)) {
416
+ logger.error(`File not found: ${filePath}`);
417
+ return null;
418
+ }
419
+ const exportNames = parseExportNames(exportNameParam);
420
+ logger.debug(`Processing exports [${exportNames.join(', ')}] with esbuild for: ${path.basename(filePath)} (target: ${target})`);
421
+ try {
422
+ // Use esbuild for transform + tree-shaking in one step
423
+ const result = await esbuild.transform(transformCodeWithExports(fs.readFileSync(filePath, 'utf-8'), exportNames), {
424
+ loader: path.extname(filePath).slice(1), // .tsx -> tsx
425
+ target: target === 'node' ? 'node18' : 'es2020',
426
+ platform: target === 'node' ? 'node' : 'browser',
427
+ format: 'esm',
428
+ jsx: 'automatic',
429
+ treeShaking: true,
430
+ minify,
431
+ sourcemap: false,
432
+ });
433
+ const transformedCode = result.code;
434
+ return transformedCode;
435
+ }
436
+ catch (esbuildError) {
437
+ logger.warn(`esbuild transform failed, falling back to TypeScript transform: ${esbuildError}`);
438
+ // Fallback to original method
439
+ const code = fs.readFileSync(filePath, 'utf-8');
440
+ try {
441
+ return transformCodeWithExports(code, exportNames);
442
+ }
443
+ catch (transformError) {
444
+ logger.warn(`TypeScript transform also failed, using simple transform: ${transformError}`);
445
+ return transformCodeSimple(code, exportNames);
446
+ }
447
+ }
342
448
  }
343
- catch (transformError) {
344
- logger.warn(`Advanced transform failed, falling back to basic transform: ${transformError}`);
345
- // ๅฆ‚ๆžœๅคฑ่ดฅ๏ผŒๅ›ž้€€ๅˆฐๅŸบๆœฌ่ฝฌๆข
346
- return transformCode(code, renderType);
449
+ catch (error) {
450
+ logger.error(`Error processing module ${id}:`, error);
347
451
  }
348
- }
349
- catch (error) {
350
- logger.error(`Error processing module ${id}:`, error);
351
- }
352
- return null;
452
+ return null;
453
+ },
353
454
  },
354
- };
455
+ // ๐Ÿ› ๏ธ PLUGIN 2: POST-BUILD PROCESSOR
456
+ {
457
+ name: 'vite-plugin-code-splitter-post-build',
458
+ apply: 'build',
459
+ enforce: 'post',
460
+ /**
461
+ * ๐Ÿ”ง PHASE 3: POST-BUILD OPTIMIZATION
462
+ * Processes generated files based on their target environment
463
+ */
464
+ async writeBundle(options) {
465
+ if (!transpileBuiltinModule) {
466
+ logger.info('Transpile builtin module is disabled, skipping post-build processing');
467
+ return;
468
+ }
469
+ // ็”จไบŽๅญ˜ๅ‚จๆฏไธชๆ ผๅผ็š„ chunks ๆ˜ ๅฐ„
470
+ const allChunksMap = {};
471
+ const getExportConfig = (fileName) => {
472
+ // Extract export type from filename pattern: Component(ExportType).js
473
+ const match = fileName.match(/\(([^)]+)\)\.js$/);
474
+ if (!match || !match[1]) {
475
+ return null; // Regular component file, defaults to browser
476
+ }
477
+ const exportName = match[1];
478
+ return EXPORT_CONFIGS.find((config) => config.exportName === exportName || config.exportName.toLowerCase().includes(exportName.toLowerCase()));
479
+ };
480
+ /**
481
+ * ๐ŸŽฏ FILE TYPE CLASSIFIER
482
+ * Determines if a generated file should be treated as Node.js target
483
+ * Examples: Timeline(getServerSideProps).js โ†’ Node.js target
484
+ * Timeline.js โ†’ Browser target
485
+ */
486
+ const isNodeTargetFile = (fileName) => {
487
+ // Look up target environment in EXPORT_CONFIGS
488
+ const config = getExportConfig(fileName);
489
+ return config?.target === 'node';
490
+ };
491
+ // ๆ”ถ้›†ๆ‰€ๆœ‰ Node.js ็›ฎๆ ‡ๆ–‡ไปถ๏ผŒๅ‡†ๅค‡้‡ๆ–ฐๆž„ๅปบ
492
+ const nodeTargetFiles = [];
493
+ // ๆ‰ซๆๆž„ๅปบ่พ“ๅ‡บ๏ผŒๆ‰พๅˆฐ Node.js ็›ฎๆ ‡ๆ–‡ไปถ
494
+ const scanForNodeFiles = (dir) => {
495
+ const files = readdirSync(dir);
496
+ files.forEach((file) => {
497
+ const filePath = path.join(dir, file);
498
+ const stats = statSync(filePath);
499
+ if (stats.isDirectory()) {
500
+ scanForNodeFiles(filePath); // ้€’ๅฝ’ๅค„็†ๅญ็›ฎๅฝ•
501
+ }
502
+ else if (file.endsWith('.js') && isNodeTargetFile(file)) {
503
+ logger.debug(`Found Node.js target file: ${file}, external: ${getExportConfig(file)?.external?.length ?? 0}`);
504
+ nodeTargetFiles.push({
505
+ filePath,
506
+ external: getExportConfig(file)?.external || [],
507
+ format: getExportConfig(file)?.format,
508
+ });
509
+ }
510
+ });
511
+ };
512
+ formats.forEach((format) => {
513
+ const formatDir = path.resolve(options.dir || 'dist', `${format}`);
514
+ if (existsSync(formatDir)) {
515
+ scanForNodeFiles(formatDir);
516
+ }
517
+ });
518
+ // ๅฆ‚ๆžœๆœ‰ Node.js ็›ฎๆ ‡ๆ–‡ไปถ๏ผŒไธบๅฎƒไปฌ้ขๅค–่งฆๅ‘ Vite build
519
+ if (nodeTargetFiles.length > 0) {
520
+ logger.info(`Found ${nodeTargetFiles.length} Node.js target files, triggering clean Vite build...`);
521
+ await Promise.all(nodeTargetFiles.map(async ({ filePath, external, format }) => {
522
+ try {
523
+ const fileName = path.basename(filePath, '.js');
524
+ logger.debug(`Rebuilding Node.js target file: ${fileName}`);
525
+ // ไฝฟ็”จ esbuild ่ฟ›่กŒๅ•ๆ–‡ไปถๆ‰“ๅŒ…
526
+ const result = await esbuild.build({
527
+ entryPoints: [filePath],
528
+ bundle: !skipBundleNodeTarget, // ๆ˜ฏๅฆๆ‰“ๅŒ…ๆ‰€ๆœ‰ไพ่ต–
529
+ platform: 'node', // Node.js ๅนณๅฐ
530
+ format: format ?? 'cjs', // CommonJS ๆ ผๅผ
531
+ target: 'node18', // Node.js ็›ฎๆ ‡็‰ˆๆœฌ
532
+ outfile: filePath, // ็›ดๆŽฅ่ฆ†็›–ๅŽŸๆ–‡ไปถ
533
+ allowOverwrite: true, // ๅ…่ฎธ่ฆ†็›–่พ“ๅ…ฅๆ–‡ไปถ
534
+ external: external ?? [], // ๆŽ’้™ค็š„ไพ่ต–
535
+ minify, // ๅฏๅ‡ๅฐ‘ 50-70% ๅคงๅฐ
536
+ treeShaking: true, // ๅŽป้™คๆœชไฝฟ็”จไปฃ็ 
537
+ sourcemap: false, // ไธ็”Ÿๆˆ sourcemap
538
+ write: true, // ็›ดๆŽฅๅ†™ๅ…ฅๆ–‡ไปถ
539
+ logLevel: 'error', // ๅชๆ˜พ็คบ้”™่ฏฏ
540
+ // ่งฃๆž JSX ๅ’Œ TypeScript
541
+ loader: {
542
+ '.tsx': 'tsx',
543
+ '.ts': 'ts',
544
+ '.jsx': 'jsx',
545
+ '.js': 'js',
546
+ },
547
+ });
548
+ if (result.errors.length === 0) {
549
+ logger.info(`Successfully rebuilt ${fileName} as single CJS file using esbuild`);
550
+ }
551
+ else {
552
+ logger.error(`esbuild errors for ${fileName}:`, result.errors);
553
+ }
554
+ }
555
+ catch (error) {
556
+ logger.error(`Failed to rebuild Node.js target file ${filePath}:`, error);
557
+ }
558
+ }));
559
+ }
560
+ // ๅฏน็”Ÿๆˆ็š„ JavaScript ๆ–‡ไปถ่ฟ›่กŒ transpileModule ๅค„็†
561
+ const transpileBuiltinModuleFn = (dir, format) => {
562
+ const files = readdirSync(dir);
563
+ files.forEach((file) => {
564
+ const filePath = path.join(dir, file);
565
+ const stats = statSync(filePath);
566
+ if (stats.isDirectory()) {
567
+ transpileBuiltinModuleFn(filePath, format); // ้€’ๅฝ’ๅค„็†ๅญ็›ฎๅฝ•
568
+ }
569
+ else if (file.endsWith('.js')) {
570
+ try {
571
+ // ่ทณ่ฟ‡ node target ๆ–‡ไปถ็š„ transpile
572
+ if (isNodeTargetFile(file)) {
573
+ logger.info(`Skipping transpile for Node.js target file: ${file}`);
574
+ return;
575
+ }
576
+ const script = readFileSync(filePath, 'utf8');
577
+ // @ts-ignore
578
+ const chunks = analyzeFileChunks(ts, script);
579
+ // ensure chunksMap is with all chunks
580
+ allChunksMap[basename(filePath)] = new Set(chunks);
581
+ const moduleMap = {
582
+ es: ts.ModuleKind.ESNext,
583
+ cjs: ts.ModuleKind.CommonJS,
584
+ umd: ts.ModuleKind.ESNext,
585
+ };
586
+ const code = ts.transpileModule(script, {
587
+ compilerOptions: {
588
+ jsx: ts.JsxEmit.React,
589
+ target: ts.ScriptTarget.ES2016,
590
+ module: moduleMap[format],
591
+ },
592
+ transformers: {
593
+ // @ts-ignore
594
+ before: [createBuiltinModuleTransformer(ts)],
595
+ },
596
+ }).outputText;
597
+ writeFileSync(filePath, code);
598
+ logger.info(`Transpiled browser target file: ${filePath}`);
599
+ }
600
+ catch (error) {
601
+ logger.error(`Failed to transpile ${filePath}:`, error);
602
+ }
603
+ }
604
+ });
605
+ // ่ฟ”ๅ›žไธ€ไธช Promise ไปฅไพฟ await
606
+ return new Promise((resolve, reject) => {
607
+ try {
608
+ resolve();
609
+ }
610
+ catch (error) {
611
+ reject(error);
612
+ }
613
+ });
614
+ };
615
+ // ่Žทๅ–่พ“ๅ‡บ็›ฎๅฝ•
616
+ const outDir = options.dir || 'dist';
617
+ const limit = pLimit(20);
618
+ // ไฝฟ็”จ Promise.all ็ญ‰ๅพ…ๆ‰€ๆœ‰ๆ ผๅผๅค„็†ๅฎŒๆˆ
619
+ await Promise.all(formats.map(async (format) => {
620
+ const formatDir = path.resolve(outDir, `${format}`);
621
+ const chunkDir = path.resolve(formatDir, 'chunks');
622
+ // if not exists, create it
623
+ if (!existsSync(chunkDir)) {
624
+ mkdirSync(chunkDir, { recursive: true });
625
+ }
626
+ await limit(() => transpileBuiltinModuleFn(formatDir, format));
627
+ }));
628
+ // ็Žฐๅœจๅฏไปฅๅค„็†ๅฎŒๆ•ดไพ่ต–้“พ
629
+ logger.info('All transpile tasks done, start to handle dependency chain...');
630
+ // ๅœจ่ฟ™้‡Œๅค„็† allChunksMap ็š„ๅฎŒๆ•ดไพ่ต–้“พ
631
+ Object.entries(allChunksMap).forEach(([entryFile, directDeps]) => {
632
+ // ๆ”ถ้›†ๆ‰€ๆœ‰้—ดๆŽฅไพ่ต–
633
+ const allDeps = [...directDeps];
634
+ // ้€’ๅฝ’ๅฏปๆ‰พ้—ดๆŽฅไพ่ต–
635
+ function findTransitiveDeps(chunks) {
636
+ chunks.forEach((chunk) => {
637
+ // ๅฆ‚ๆžœ่ฏฅchunkๆœฌ่บซไนŸๆ˜ฏไธชๅ…ฅๅฃ(ๆœ‰ไพ่ต–ๅˆ—่กจ)
638
+ if (chunk in allChunksMap) {
639
+ const subDeps = allChunksMap[chunk];
640
+ // ๆทปๅŠ ๆœชๅŒ…ๅซ็š„ไพ่ต–
641
+ if (subDeps) {
642
+ subDeps.forEach((dep) => {
643
+ if (!allDeps.includes(dep)) {
644
+ allDeps.push(dep);
645
+ // ้€’ๅฝ’ๅฏปๆ‰พ่ฟ™ไธชไพ่ต–็š„ไพ่ต–
646
+ findTransitiveDeps([dep]);
647
+ }
648
+ });
649
+ }
650
+ }
651
+ });
652
+ }
653
+ // ๅผ€ๅง‹้€’ๅฝ’ๆŸฅๆ‰พ
654
+ findTransitiveDeps([...directDeps]);
655
+ // ๆ›ดๆ–ฐไธบๅฎŒๆ•ดไพ่ต–ๅˆ—่กจ
656
+ allChunksMap[entryFile] = new Set(allDeps);
657
+ });
658
+ // ่ฝฌๆข Set ไธบๆ•ฐ็ป„ไปฅไพฟๆญฃ็กฎๅบๅˆ—ๅŒ–
659
+ const serializedMap = Object.entries(allChunksMap).reduce((result, [key, deps]) => {
660
+ result[key] = Array.from(deps);
661
+ return result;
662
+ }, {});
663
+ formats.forEach((format) => {
664
+ // ๅญ˜ไธ‹ๆฅ
665
+ writeFileSync(path.join(outDir, `${format}/chunks-map.json`), JSON.stringify(serializedMap, null, 2));
666
+ });
667
+ },
668
+ },
669
+ ];
355
670
  }
356
671
  export default vitePluginCodeSplitter;