@htyf-mp/cli 0.2.0-beta.1 → 0.2.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.
@@ -7,30 +7,26 @@ import TerserPlugin from 'terser-webpack-plugin';
7
7
  import * as Repack from '@callstack/repack';
8
8
  import { ExpoModulesPlugin } from '@callstack/repack-plugin-expo-modules';
9
9
  import { ReanimatedPlugin } from '@callstack/repack-plugin-reanimated';
10
+ import { HtyfModulesPlugin } from './HtyfImportsPlugin.mjs';
11
+ import { getProjectRoot } from './utils.mjs';
10
12
 
11
13
  // 获取当前文件和目录路径
12
14
  const __filename = fileURLToPath(import.meta.url);
13
15
  const __dirname = path.dirname(__filename);
14
- const projectPath = path.resolve(__dirname, '../../../');
15
16
 
16
17
  // 微前端暴露配置,通常为JSON字符串
17
18
  const appExposesOptions = process.env.APP_EXPOSES_OPTIONS;
19
+ const appRootIndexPath = process.env.APP_ROOT_INDEX_PATH;
18
20
 
19
- // 读取package.json,获取应用基本信息
20
- const appConfig = fse.readJsonSync(path.join(projectPath, './app.json'));
21
21
  // 读取依赖共享配置
22
22
  const dependencies = fse.readJsonSync(path.join(__dirname, './shared-output.json'));
23
- const htyf = appConfig.htyf || {}
24
- const htyfHostConfig = {
25
- "version": htyf.version,
26
- "name": htyf.name,
27
- "appid": htyf.appid,
28
- "appUrlConfig": htyf.appUrlConfig,
29
- "zipUrl": htyf.zipUrl
30
- }
23
+
31
24
  // 记录构建时间戳
32
25
  const time = Date.now();
33
26
 
27
+ const version = process.env.APP_VERSION;
28
+ const appid = process.env.APP_ID;
29
+
34
30
  // 构建Module Federation共享依赖对象
35
31
  const sharedObj = {};
36
32
  for (const key in dependencies) {
@@ -39,11 +35,13 @@ for (const key in dependencies) {
39
35
  /** 是否运行为单例 */
40
36
  singleton: true,
41
37
  /** host模式下必须为true,微应用为false */
42
- eager: false,
43
- requiredVersion: p.version,
38
+ eager: true,
39
+ version: p.version,
44
40
  };
45
41
  }
46
42
 
43
+ const projectRoot = getProjectRoot();
44
+
47
45
  /**
48
46
  * Webpack配置导出函数
49
47
  * @param {object} env - 构建环境变量
@@ -51,9 +49,9 @@ for (const key in dependencies) {
51
49
  */
52
50
  export default (env = {}) => {
53
51
  // 设置构建上下文
54
- env.context = __dirname;
55
- const { entry = './index.js', platform = 'ios' } = env;
56
- const context = env.context || projectPath;
52
+ env.context = projectRoot || __dirname;
53
+ const { entry = appRootIndexPath, platform = 'ios' } = env;
54
+ const context = env.context || __dirname;
57
55
  // 获取Repack的resolve配置
58
56
  const _options = Repack.getResolveOptions();
59
57
 
@@ -62,30 +60,67 @@ export default (env = {}) => {
62
60
  if (appExposesOptions) {
63
61
  mpOptions = JSON.parse(decodeURI(appExposesOptions));
64
62
  }
65
-
63
+
64
+ // 处理 exposes 路径,确保路径正确解析
65
+ // Module Federation 需要相对于 context 的路径,如果路径不以 ./ 开头,需要添加
66
+ if (mpOptions.exposes) {
67
+ const processedExposes = {};
68
+ for (const [key, value] of Object.entries(mpOptions.exposes)) {
69
+ // 如果路径不是绝对路径且不以 ./ 开头,添加 ./ 前缀
70
+ if (value && typeof value === 'string' && !path.isAbsolute(value) && !value.startsWith('./') && !value.startsWith('../')) {
71
+ processedExposes[key] = `./${value}`;
72
+ } else {
73
+ processedExposes[key] = value;
74
+ }
75
+ }
76
+ mpOptions.exposes = processedExposes;
77
+ }
78
+
79
+ console.log('\n');
80
+ console.log('=====mpOptions (after processing)=====');
81
+ console.log('\n');
82
+ console.log('context:', projectRoot);
83
+ console.log('\n');
84
+ console.log('entry:', entry);
85
+ console.log('\n');
86
+ console.log('exposes (processed):', JSON.stringify(mpOptions.exposes, null, 2));
87
+ console.log('\n');
88
+ console.log(JSON.stringify(mpOptions, null, 2));
89
+ console.log('\n');
90
+ console.log('=====mpOptions=====');
91
+ console.log('\n');
66
92
  // 基础Webpack配置
67
93
  const mpConfig = {
68
94
  context,
69
95
  entry,
70
96
  resolve: {
71
97
  ..._options,
98
+ // 确保扩展名解析正确
99
+ extensions: ['.tsx', '.ts', '.jsx', '.js', '.json', ...(_options.extensions || [])],
72
100
  },
73
101
  output: {
74
102
  // 默认输出路径,host模式下会被覆盖
75
103
  path: mpOptions.outputPath,
76
- uniqueName: mpOptions.uniqueName,
104
+ publicPath: mpOptions.outputPath,
105
+ // uniqueName: `htyf_mp_for_${mpOptions.name}`,
77
106
  },
78
107
  module: {
79
108
  rules: [
80
109
  // 处理js/ts/tsx文件,使用babel-loader
81
110
  {
82
111
  test: /\.[cm]?[jt]sx?$/,
83
- use: 'babel-loader',
112
+ use: {
113
+ loader: 'babel-loader',
114
+ // options: {
115
+ // configFile: path.resolve(__dirname, './babel.config.js'),
116
+ // }
117
+ },
84
118
  type: 'javascript/auto',
85
119
  },
86
120
  // 处理静态资源(图片等),支持inline模式
87
121
  ...Repack.getAssetTransformRules({
88
- inline: true,
122
+ inline: !!mpOptions.extraChunksPath,
123
+ svg: 'xml',
89
124
  }),
90
125
  ],
91
126
  },
@@ -107,12 +142,12 @@ export default (env = {}) => {
107
142
  plugins: [
108
143
  // 注入全局常量(appid、版本、构建时间)
109
144
  new webpack.DefinePlugin({
110
- __APP_DEFINE_APPID__: `"${htyfHostConfig.name}"`,
111
- __APP_DEFINE_VERSION__: `"${htyfHostConfig.version}"`,
145
+ __APP_DEFINE_APPID__: `"${appid}"`,
146
+ __APP_DEFINE_VERSION__: `"${version}"`,
112
147
  __APP_DEFINE_BUILD_TIME__: `"${time}"`,
113
148
  }),
114
149
  // Repack主插件,支持extraChunks配置
115
- new Repack.RepackPlugin(mpOptions.extraChunksPath ? {
150
+ new Repack.RepackPlugin({
116
151
  platform,
117
152
  extraChunks: [
118
153
  {
@@ -121,15 +156,14 @@ export default (env = {}) => {
121
156
  outputPath: `${mpOptions.extraChunksPath}`,
122
157
  },
123
158
  ],
124
- } : {
125
- platform,
126
159
  }),
127
160
  // Expo模块支持
128
161
  new ExpoModulesPlugin(),
129
162
  // Reanimated支持
130
163
  new ReanimatedPlugin(),
131
164
  // Module Federation V2插件,支持host和remote两种模式
132
- new Repack.plugins.ModuleFederationPluginV2(mpOptions.exposes ? {
165
+ new Repack.plugins.ModuleFederationPluginV2({
166
+ dts: false,
133
167
  // 微应用模式
134
168
  name: mpOptions.name,
135
169
  filename: mpOptions.filename,
@@ -140,22 +174,18 @@ export default (env = {}) => {
140
174
  'react-native': { singleton: true, eager: true },
141
175
  },
142
176
  shareStrategy: 'loaded-first',
143
- } : {
144
- // host模式
145
- name: 'HTYF_HOST_APP',
146
- remotes: {},
147
- shared: {
148
- ...sharedObj,
149
- react: { singleton: true, eager: true },
150
- 'react-native': { singleton: true, eager: true },
151
- },
152
- shareStrategy: 'loaded-first',
177
+ }),
178
+ new HtyfModulesPlugin({
179
+ codeSigning: true,
180
+ appid: mpOptions.appid,
181
+ version: mpOptions.version,
182
+ manifest: mpOptions.manifest,
153
183
  }),
154
184
  new webpack.optimize.LimitChunkCountPlugin({
155
185
  maxChunks: 2, // 限制最大chunk数,便于远程加载
156
- }),
186
+ })
157
187
  ],
158
188
  };
159
189
 
160
190
  return mpConfig;
161
- };
191
+ };
package/cli.js DELETED
@@ -1,288 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import chalk from 'chalk'
4
- import path from 'path'
5
- import fs from 'fs'
6
- import fse from 'fs-extra'
7
- import { Command } from 'commander'
8
- import select from '@inquirer/select'
9
- import { input } from '@inquirer/prompts';
10
- import {execa, execaCommand} from 'execa'
11
- import ora from 'ora'
12
- import childProcess from 'child_process'
13
- import { v4 as uuidv4 } from 'uuid';
14
- import md5 from 'md5'
15
- import os from 'os'
16
-
17
- const v4options = {
18
- random: [
19
- 0x10, 0x91, 0x56, 0xbe, 0xc4, 0xfb, 0xc1, 0xea, 0x71, 0xb4, 0xef, 0xe1, 0x67, 0x1c, 0x58, 0x36,
20
- ],
21
- };
22
-
23
- const log = console.log
24
- const program = new Command()
25
- const green = chalk.green
26
-
27
- const repoUrl = 'https://github.com/htyf-mp-community/htyf-mp.git'
28
- const codingRepoUrl = 'https://e.coding.net/dagouzhi/hongtangyun_mobile/htyf-mp.git'
29
-
30
- const taroTempPath = 'mini-apps-template';
31
- const expoTempPath = 'mini-apps-template';
32
- const gameTempPath = 'mini-game-template';
33
-
34
- const isYarnInstalled = () => {
35
- try {
36
- childProcess.execSync('yarn --version');
37
- return true;
38
- } catch {
39
- return false;
40
- }
41
- }
42
-
43
- const isBunInstalled = () => {
44
- try {
45
- childProcess.execSync('bun --version')
46
- return true;
47
- } catch(err) {
48
- return false;
49
- }
50
- }
51
-
52
- async function main() {
53
- const spinner = ora({
54
- text: '创建代码库'
55
- })
56
- try {
57
- const kebabRegez = /^([a-z]+)(-[a-z0-9]+)*$/
58
-
59
- program
60
- .name('红糖云服APP-小程序')
61
- .description('快速创建红糖云服APP小程序')
62
-
63
- program.parse(process.argv)
64
-
65
- const args = program.args
66
- let appName = args[0]
67
-
68
- if (!appName || !kebabRegez.test(args[0])) {
69
- appName = await input({
70
- message: '输入应用程序目录名称',
71
- default: 'my-htyf-mp',
72
- validate: d => {
73
- if(!kebabRegez.test(d)) {
74
- return '请以my-app-name格式输入您的应用程序英文目录名称'
75
- }
76
- return true
77
- }
78
- })
79
- }
80
-
81
- const displayNameRegez = /^[\u4e00-\u9fa5a-zA-Z0-9]{2,4}$/
82
-
83
- let displayName = ''
84
- if (!displayName) {
85
- displayName = await input({
86
- message: '输入应用程序名称',
87
- default: '小程序',
88
- validate: d => {
89
- if(!displayNameRegez.test(d)) {
90
- return '只能中文、字母和数字, 不能包含特殊字符, 限2-4个字符'
91
- }
92
- return true
93
- }
94
- })
95
- }
96
-
97
- const tempType = await select({
98
- message: '请选择模板类型?',
99
- choices: [
100
- {
101
- name: 'Expo (https://docs.expo.dev/)',
102
- value: 'expo',
103
- },
104
- {
105
- name: 'Taro (https://docs.taro.zone/)',
106
- value: 'taro',
107
- },
108
- {
109
- name: 'Game (https://docs.cocos.com/)',
110
- value: 'game-cocos',
111
- }
112
- ]
113
- })
114
-
115
- const repoType = await select({
116
- message: '请选择模板镜像?',
117
- choices: [
118
- {
119
- name: 'github(最新)',
120
- value: repoUrl,
121
- },
122
- {
123
- name: 'coding(最快)',
124
- value: codingRepoUrl,
125
- },
126
- ]
127
- })
128
-
129
- const appid = md5(uuidv4(v4options));
130
-
131
- let config = {
132
- "type": "app",
133
- "name": appName,
134
- "projectname": displayName,
135
- "appid": appid,
136
- "appUrlConfig": "https://xxx.cos.ap-chengdu.myqcloud.com/assets/testMiniApps/htyanimation/app.json",
137
- "zipUrl": "https://xxxxx.myqcloud.com/assets/testMiniApps/xxxxx/outputs/dist.dgz"
138
- }
139
-
140
- log(`\n初始化项目. \n`)
141
-
142
-
143
- const root_path = path.join(process.cwd(), `${appName}`);
144
- const isExists = fs.existsSync(root_path);
145
- if (isExists) {
146
- console.error('目录已经存在。')
147
- return;
148
- }
149
- if (/[\u4e00-\u9fa5]/gi.test(root_path)) {
150
- console.error('禁止目录中包含中文字符')
151
- return;
152
- }
153
- spinner.start()
154
- const tmpdir = path.join(os.tmpdir(), md5(`__HTYF__`), appName);
155
- fse.emptyDirSync(tmpdir)
156
- await execa('git', ['clone', repoType, tmpdir])
157
- let appRootPath = '';
158
- const taro_path = path.join(tmpdir, `${taroTempPath}`)
159
- const expo_path = path.join(tmpdir, `${expoTempPath}`)
160
- const game_path = path.join(tmpdir, `${gameTempPath}`)
161
- const cli_path = path.join(tmpdir, `cli`)
162
- const git_path = path.join(tmpdir, `.git`)
163
- const docs_path = path.join(tmpdir, `docs`)
164
- const README_path = path.join(tmpdir, `README.md`)
165
- try {
166
- if (tempType === 'taro') {
167
- appRootPath = taro_path
168
- fse.removeSync(game_path)
169
- }
170
- if (tempType === 'expo') {
171
- appRootPath = expo_path
172
- fse.removeSync(game_path)
173
- }
174
- if (tempType === 'game-cocos') {
175
- appRootPath = game_path
176
- fse.removeSync(taro_path)
177
- fse.removeSync(expo_path)
178
- }
179
- fse.removeSync(cli_path)
180
- fse.removeSync(git_path)
181
- fse.removeSync(docs_path)
182
- fse.removeSync(README_path)
183
- } catch (err) {}
184
- const __TEMP_PATH__ = path.join(appRootPath, '__TEMP__');
185
- const pkg_path = path.join(appRootPath, './package.json');
186
- const dgzJosn = path.join(`${appRootPath}`, 'project.dgz.json')
187
- console.log(' ')
188
- console.log(tempType)
189
- console.log(__TEMP_PATH__)
190
- console.log(' ')
191
- if (tempType === 'taro') {
192
- fse.copySync(
193
- path.join(__TEMP_PATH__, 'taro_src'),
194
- path.join(appRootPath, './src')
195
- )
196
- fse.copySync(
197
- path.join(__TEMP_PATH__, 'taro.App.tsx'),
198
- path.join(appRootPath, './App.tsx')
199
- )
200
- fse.copySync(
201
- path.join(__TEMP_PATH__, 'taro.index.js'),
202
- path.join(appRootPath, './index.js')
203
- )
204
- fse.copySync(
205
- path.join(__TEMP_PATH__, 'taro.babel.config.js'),
206
- path.join(appRootPath, './babel.config.js')
207
- )
208
- fse.copySync(
209
- path.join(__TEMP_PATH__, 'taro.project.dgz.json'),
210
- path.join(appRootPath, './project.dgz.json')
211
- )
212
- const pkg_info = fse.readJSONSync(pkg_path)
213
- pkg_info['scripts'] = {
214
- ...(pkg_info['scripts'] || {}),
215
- "build:weapp": "taro build --type weapp",
216
- "build:swan": "taro build --type swan",
217
- "build:alipay": "taro build --type alipay",
218
- "build:tt": "taro build --type tt",
219
- "build:h5": "taro build --type h5",
220
- "build:rn": "taro build --type rn",
221
- "build:qq": "taro build --type qq",
222
- "build:jd": "taro build --type jd",
223
- "build:quickapp": "taro build --type quickapp",
224
- "dev:weapp": "npm run build:weapp -- --watch",
225
- "dev:swan": "npm run build:swan -- --watch",
226
- "dev:alipay": "npm run build:alipay -- --watch",
227
- "dev:tt": "npm run build:tt -- --watch",
228
- "dev:h5": "npm run build:h5 -- --watch",
229
- "dev:rn": "npm run build:rn -- --watch",
230
- "dev:qq": "npm run build:qq -- --watch",
231
- "dev:jd": "npm run build:jd -- --watch",
232
- "dev:quickapp": "npm run build:quickapp -- --watch",
233
- }
234
- delete pkg_info['scripts']['web']
235
- delete pkg_info['scripts']['web:dev']
236
- fse.writeJSONSync(pkg_path, pkg_info, {
237
- spaces: 2
238
- })
239
- }
240
- if (tempType === 'expo') {
241
- fse.copySync(
242
- path.join(__TEMP_PATH__, 'expo_src'),
243
- path.join(appRootPath, './src')
244
- )
245
- fse.copySync(
246
- path.join(__TEMP_PATH__, 'expo.App.tsx'),
247
- path.join(appRootPath, './App.tsx')
248
- )
249
- fse.copySync(
250
- path.join(__TEMP_PATH__, 'expo.index.js'),
251
- path.join(appRootPath, './index.js')
252
- )
253
- fse.copySync(
254
- path.join(__TEMP_PATH__, 'expo.babel.config.js'),
255
- path.join(appRootPath, './babel.config.js')
256
- )
257
- fse.copySync(
258
- path.join(__TEMP_PATH__, 'expo.project.dgz.json'),
259
- path.join(appRootPath, './project.dgz.json')
260
- )
261
- }
262
- if (tempType === 'game-cocos') {
263
-
264
- }
265
-
266
- fs.writeFileSync(dgzJosn, JSON.stringify(config, undefined, 2))
267
- console.log(dgzJosn)
268
-
269
- fse.removeSync(__TEMP_PATH__)
270
-
271
- spinner.text = ''
272
-
273
- fse.moveSync(appRootPath, root_path)
274
-
275
- spinner.stop()
276
- log(`${green.bold('Success!')} 创建项目 ${appName} 在 ${root_path} \n`)
277
- log(`切换到【 ${root_path} 】目录并运行开发`)
278
- log('\n')
279
- } catch (err) {
280
- console.log(err)
281
- log('\n')
282
- if (err.exitCode == 128) {
283
- log('Error: 目录已经存在。')
284
- }
285
- spinner.stop()
286
- }
287
- }
288
- main()
@@ -1,98 +0,0 @@
1
-
2
- import path from 'path';
3
- import fse from 'fs-extra'
4
- import { fileURLToPath } from 'url'
5
-
6
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
7
- const sharedPkg = fse.readJSONSync(path.join(__dirname, 'shared-output.json'))
8
-
9
- function trimTemplateStrings(strings, ...values) {
10
- let fullString = strings.reduce((acc, str, i) => acc + str + (values[i] || ''), '');
11
- return fullString.replace(/^[ \t]+/gm, '');
12
- }
13
-
14
- export class AnalyzeThirdPartyModulesPlugin {
15
- constructor(appConfig) {
16
- this.config = appConfig;
17
- }
18
- apply(compiler) {
19
- const config = this.config;
20
- compiler.hooks.emit.tapAsync('AnalyzeThirdPartyModulesPlugin', (compilation, callback) => {
21
- let thirdPartyModules = new Set();
22
-
23
- compilation.modules.forEach(module => {
24
- if (module.context && module.context.includes('node_modules')) {
25
- let packageName = module.context.split('node_modules/')[1].split('/')[0];
26
- if (packageName.startsWith('@')) {
27
- packageName = module.context.split('node_modules/')[1].split('/').splice(0,2).join('/');
28
- }
29
- const ios_path = path.join(__dirname, '../node_modules/', packageName, 'ios');
30
- const and_path = path.join(__dirname, '../node_modules/', packageName, 'android');
31
- const isNative = fse.pathExistsSync(ios_path) || fse.pathExistsSync(and_path);
32
- if (isNative) {
33
- thirdPartyModules.add(packageName);
34
- }
35
- }
36
- });
37
- const nonsupport = []
38
- thirdPartyModules = Array.from(thirdPartyModules);
39
- for (const key in thirdPartyModules) {
40
- const pkg = thirdPartyModules[key];
41
- if (!sharedPkg[pkg]) {
42
- if (
43
- pkg !== 'expo-splash-screen' &&
44
- pkg !== '@callstack/repack' &&
45
- pkg !== 'react-native-iap'
46
- ) {
47
- nonsupport.push(pkg)
48
- }
49
- }
50
- }
51
- if (nonsupport.length) {
52
- const msgArr = ['红糖云服暂不支持以上第三方native组件']
53
- console.log(' ')
54
- console.log('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
55
- console.log('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
56
- console.log(' ')
57
- console.log('红糖云服暂不支持以下第三方native组件: ')
58
- for (const key in nonsupport) {
59
- const element = nonsupport[key];
60
- const _msg = `${Number(key) + 1}. ${element}`;
61
- console.log(_msg)
62
- msgArr.push(_msg)
63
- }
64
- console.log(' ')
65
- console.log('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
66
- console.log('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
67
- console.log(' ')
68
- console.log(' ')
69
- callback(msgArr.join(`\n`));
70
- } else {
71
- callback();
72
- }
73
- });
74
- if (config.codeSigning) {
75
- compiler.hooks.emit.tapAsync('WrapCodePlugin', (compilation, callback) => {
76
- // 遍历所有编译过的资源文件
77
- for (const filename in compilation.assets) {
78
- if (compilation.assets.hasOwnProperty(filename)) {
79
- // 获取文件内容
80
- let source = compilation.assets[filename].source();
81
-
82
- // 包裹代码
83
- const wrappedSource = trimTemplateStrings`
84
- ${source}
85
- `;
86
-
87
- // 更新文件内容
88
- compilation.assets[filename] = {
89
- source: () => wrappedSource,
90
- size: () => wrappedSource.length
91
- };
92
- }
93
- }
94
- callback();
95
- });
96
- }
97
- }
98
- }