@quicktvui/web-cli 1.0.0-beta.3 → 1.0.0-beta.31
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/bin/qt-web-cli.js +316 -0
- package/lib/entry-package.js +66 -8
- package/lib/webpack.config.js +24 -46
- package/package.json +24 -18
- package/templates/web-renderer.html +11 -0
- package/bin/qt-web-dev.js +0 -160
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* QuickTVUI Web Dev CLI
|
|
5
|
+
* 启动 web 开发服务器,无需手动配置入口文件
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('path')
|
|
9
|
+
const fs = require('fs')
|
|
10
|
+
const signale = require('signale')
|
|
11
|
+
const minimist = require('minimist')
|
|
12
|
+
const { exec } = require('shelljs')
|
|
13
|
+
|
|
14
|
+
// 解析命令行参数
|
|
15
|
+
const args = minimist(process.argv.slice(2), {
|
|
16
|
+
string: ['port', 'config'],
|
|
17
|
+
boolean: ['help', 'open', 'v', 'info', 'version'],
|
|
18
|
+
alias: {
|
|
19
|
+
p: 'port',
|
|
20
|
+
c: 'config',
|
|
21
|
+
h: 'help',
|
|
22
|
+
o: 'open',
|
|
23
|
+
},
|
|
24
|
+
default: {
|
|
25
|
+
port: 39001,
|
|
26
|
+
open: true,
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// 显示版本信息
|
|
31
|
+
if (args.v || args.version) {
|
|
32
|
+
const cliPkg = require(path.join(__dirname, '../package.json'))
|
|
33
|
+
console.log(`@quicktvui/web-cli v${cliPkg.version}`)
|
|
34
|
+
process.exit(0)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 显示详细信息(包括 web-renderer 版本)
|
|
38
|
+
if (args.info) {
|
|
39
|
+
const cliPkg = require(path.join(__dirname, '../package.json'))
|
|
40
|
+
console.log(`@quicktvui/web-cli v${cliPkg.version}`)
|
|
41
|
+
try {
|
|
42
|
+
// 尝试多种路径查找 web-renderer
|
|
43
|
+
let rendererPkg
|
|
44
|
+
const possiblePaths = [
|
|
45
|
+
'@quicktvui/web-renderer/package.json',
|
|
46
|
+
path.join(__dirname, '../../web-renderer/package.json'),
|
|
47
|
+
path.join(__dirname, '../../../web-renderer/package.json'),
|
|
48
|
+
]
|
|
49
|
+
for (const pkgPath of possiblePaths) {
|
|
50
|
+
try {
|
|
51
|
+
rendererPkg = require(pkgPath)
|
|
52
|
+
break
|
|
53
|
+
} catch (e) {
|
|
54
|
+
// 继续尝试下一个路径
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (rendererPkg) {
|
|
58
|
+
console.log(`@quicktvui/web-renderer v${rendererPkg.version}`)
|
|
59
|
+
} else {
|
|
60
|
+
console.log('@quicktvui/web-renderer (not installed)')
|
|
61
|
+
}
|
|
62
|
+
} catch (e) {
|
|
63
|
+
console.log('@quicktvui/web-renderer (not installed)')
|
|
64
|
+
}
|
|
65
|
+
process.exit(0)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 显示帮助信息
|
|
69
|
+
if (args.help) {
|
|
70
|
+
console.log(`
|
|
71
|
+
QuickTVUI Web CLI
|
|
72
|
+
|
|
73
|
+
Usage:
|
|
74
|
+
qt-web-cli [options]
|
|
75
|
+
|
|
76
|
+
Options:
|
|
77
|
+
-p, --port <port> 开发服务器端口 (默认: 39001)
|
|
78
|
+
-c, --config <path> 自定义 webpack 配置文件路径
|
|
79
|
+
-o, --open 自动打开浏览器
|
|
80
|
+
-h, --help 显示帮助信息
|
|
81
|
+
-v, --version 显示 CLI 版本
|
|
82
|
+
--info 显示 CLI 和 web-renderer 版本
|
|
83
|
+
|
|
84
|
+
Examples:
|
|
85
|
+
qt-web-cli
|
|
86
|
+
qt-web-cli --port 8080
|
|
87
|
+
qt-web-cli --config ./my.webpack.js
|
|
88
|
+
qt-web-cli -v
|
|
89
|
+
qt-web-cli --info
|
|
90
|
+
|
|
91
|
+
特点:
|
|
92
|
+
- 自动检测项目入口 (main-native.ts/js, main.ts/js)
|
|
93
|
+
- 零配置启动 web 开发服务器
|
|
94
|
+
`)
|
|
95
|
+
process.exit(0)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function main() {
|
|
99
|
+
signale.pending('正在启动 QuickTVUI Web 开发服务器...')
|
|
100
|
+
|
|
101
|
+
// 查找项目根目录
|
|
102
|
+
const projectRoot = findProjectRoot()
|
|
103
|
+
if (!projectRoot) {
|
|
104
|
+
signale.error('无法找到项目根目录')
|
|
105
|
+
process.exit(1)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
signale.info(`项目根目录: ${projectRoot}`)
|
|
109
|
+
|
|
110
|
+
// 读取 package.json
|
|
111
|
+
let pkg = {}
|
|
112
|
+
try {
|
|
113
|
+
pkg = require(path.join(projectRoot, 'package.json'))
|
|
114
|
+
} catch (e) {}
|
|
115
|
+
|
|
116
|
+
// 检测入口文件
|
|
117
|
+
let mainEntry
|
|
118
|
+
let entryType
|
|
119
|
+
|
|
120
|
+
// 入口候选列表
|
|
121
|
+
const entryCandidates = [
|
|
122
|
+
{ name: 'src/main-native.ts', path: './src/main-native.ts', type: 'package' },
|
|
123
|
+
{ name: 'src/main-native.js', path: './src/main-native.js', type: 'package' },
|
|
124
|
+
{ name: 'src/main.ts', path: './src/main.ts', type: 'package' },
|
|
125
|
+
{ name: 'src/main.js', path: './src/main.js', type: 'package' },
|
|
126
|
+
{ name: 'main (package.json)', path: pkg.main, type: 'package' },
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
for (const candidate of entryCandidates) {
|
|
130
|
+
if (!candidate.path) continue
|
|
131
|
+
const entryFullPath = path.resolve(projectRoot, candidate.path)
|
|
132
|
+
if (fs.existsSync(entryFullPath)) {
|
|
133
|
+
mainEntry = candidate.path
|
|
134
|
+
entryType = candidate.type
|
|
135
|
+
signale.info(`主入口: ${mainEntry}`)
|
|
136
|
+
break
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!mainEntry) {
|
|
141
|
+
signale.error('无法找到有效的入口文件,请检查以下路径:')
|
|
142
|
+
signale.error(' - src/main-native.ts 或 src/main-native.js')
|
|
143
|
+
signale.error(' - src/main.ts 或 src/main.js')
|
|
144
|
+
signale.error(' - package.json 中的 main 字段')
|
|
145
|
+
process.exit(1)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const mainEntryPath = path.resolve(projectRoot, mainEntry)
|
|
149
|
+
|
|
150
|
+
// 获取 webpack 配置
|
|
151
|
+
let webpackConfigPath
|
|
152
|
+
if (args.config) {
|
|
153
|
+
webpackConfigPath = path.resolve(projectRoot, args.config)
|
|
154
|
+
} else {
|
|
155
|
+
webpackConfigPath = path.resolve(__dirname, '../lib/webpack.config.js')
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 设置环境变量
|
|
159
|
+
process.env.NODE_ENV = 'development'
|
|
160
|
+
process.env.QUICKTVUI_PROJECT_ROOT = projectRoot
|
|
161
|
+
process.env.QUICKTVUI_MAIN_ENTRY = mainEntryPath
|
|
162
|
+
process.env.QUICKTVUI_ENTRY_TYPE = entryType
|
|
163
|
+
process.env.QUICKTVUI_PORT = String(args.port)
|
|
164
|
+
|
|
165
|
+
// 检测 OpenSSL 版本
|
|
166
|
+
const needLegacyProvider = checkOpenSSLVersion(process.versions.openssl)
|
|
167
|
+
if (needLegacyProvider) {
|
|
168
|
+
signale.warn(`检测到 OpenSSL ${process.versions.openssl},已添加 --openssl-legacy-provider`)
|
|
169
|
+
process.env.NODE_OPTIONS = '--openssl-legacy-provider'
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
signale.pending(`启动开发服务器: http://localhost:${args.port}`)
|
|
173
|
+
|
|
174
|
+
// 使用 webpack Node API 启动开发服务器
|
|
175
|
+
startDevServer(webpackConfigPath, args.port, args.open)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 使用 webpack Node API 启动开发服务器
|
|
180
|
+
*/
|
|
181
|
+
function startDevServer(configPath, port, shouldOpen) {
|
|
182
|
+
const webpack = require('webpack')
|
|
183
|
+
const WebpackDevServer = require('webpack-dev-server')
|
|
184
|
+
const config = require(configPath)
|
|
185
|
+
|
|
186
|
+
// 确保 devServer 配置正确
|
|
187
|
+
config.devServer = config.devServer || {}
|
|
188
|
+
config.devServer.port = port
|
|
189
|
+
config.devServer.open = false // 我们自己控制浏览器打开
|
|
190
|
+
|
|
191
|
+
const compiler = webpack(config)
|
|
192
|
+
const server = new WebpackDevServer(config.devServer, compiler)
|
|
193
|
+
|
|
194
|
+
// 监听编译完成事件,在首次编译完成后打开浏览器
|
|
195
|
+
let isOpened = false
|
|
196
|
+
compiler.hooks.done.tap('open-browser', (stats) => {
|
|
197
|
+
if (!isOpened && shouldOpen) {
|
|
198
|
+
isOpened = true
|
|
199
|
+
const url = `http://localhost:${port}`
|
|
200
|
+
openOrRefreshBrowser(url)
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
server.startCallback((err) => {
|
|
205
|
+
if (err) {
|
|
206
|
+
signale.error('启动开发服务器失败:', err.message)
|
|
207
|
+
process.exit(1)
|
|
208
|
+
}
|
|
209
|
+
signale.success(`开发服务器已启动: http://localhost:${port}`)
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
// 处理退出信号
|
|
213
|
+
process.on('SIGINT', () => {
|
|
214
|
+
server.stopCallback(() => {
|
|
215
|
+
signale.info('开发服务器已停止')
|
|
216
|
+
process.exit(0)
|
|
217
|
+
})
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 等待服务器就绪
|
|
223
|
+
*/
|
|
224
|
+
function waitForServer(url, timeout = 30000) {
|
|
225
|
+
const http = require('http')
|
|
226
|
+
const startTime = Date.now()
|
|
227
|
+
|
|
228
|
+
return new Promise((resolve, reject) => {
|
|
229
|
+
function check() {
|
|
230
|
+
if (Date.now() - startTime > timeout) {
|
|
231
|
+
reject(new Error('等待服务器超时'))
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const req = http.get(url, (res) => {
|
|
236
|
+
if (res.statusCode === 200 || res.statusCode === 304) {
|
|
237
|
+
resolve()
|
|
238
|
+
} else {
|
|
239
|
+
setTimeout(check, 500)
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
req.on('error', () => {
|
|
244
|
+
setTimeout(check, 500)
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
req.setTimeout(1000, () => {
|
|
248
|
+
req.destroy()
|
|
249
|
+
setTimeout(check, 500)
|
|
250
|
+
})
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
check()
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* 打开或刷新浏览器(支持 macOS)
|
|
259
|
+
*/
|
|
260
|
+
function openOrRefreshBrowser(url) {
|
|
261
|
+
const platform = process.platform
|
|
262
|
+
|
|
263
|
+
if (platform === 'darwin') {
|
|
264
|
+
// macOS: 使用 AppleScript 检查并刷新已存在的标签页
|
|
265
|
+
const appleScript = `
|
|
266
|
+
tell application "Safari"
|
|
267
|
+
activate
|
|
268
|
+
set found to false
|
|
269
|
+
repeat with w in windows
|
|
270
|
+
repeat with t in tabs of w
|
|
271
|
+
if URL of t starts with "http://localhost:" then
|
|
272
|
+
set URL of t to "${url}"
|
|
273
|
+
set found to true
|
|
274
|
+
exit repeat
|
|
275
|
+
end if
|
|
276
|
+
end repeat
|
|
277
|
+
if found then exit repeat
|
|
278
|
+
end repeat
|
|
279
|
+
if not found then
|
|
280
|
+
open location "${url}"
|
|
281
|
+
end if
|
|
282
|
+
end tell
|
|
283
|
+
`
|
|
284
|
+
|
|
285
|
+
exec(`osascript -e '${appleScript.replace(/\n/g, ' ')}'`, { silent: true })
|
|
286
|
+
signale.success('已打开/刷新浏览器')
|
|
287
|
+
} else {
|
|
288
|
+
// 其他平台:直接打开 URL
|
|
289
|
+
const openCmd = platform === 'win32' ? 'start' : 'xdg-open'
|
|
290
|
+
exec(`${openCmd} "${url}"`, { silent: true })
|
|
291
|
+
signale.success('已打开浏览器')
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function findProjectRoot(startDir = process.cwd()) {
|
|
296
|
+
let currentDir = startDir
|
|
297
|
+
while (currentDir !== path.dirname(currentDir)) {
|
|
298
|
+
if (fs.existsSync(path.join(currentDir, 'package.json'))) {
|
|
299
|
+
return currentDir
|
|
300
|
+
}
|
|
301
|
+
currentDir = path.dirname(currentDir)
|
|
302
|
+
}
|
|
303
|
+
return null
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function checkOpenSSLVersion(version) {
|
|
307
|
+
if (!version) return false
|
|
308
|
+
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version)
|
|
309
|
+
if (!match) return false
|
|
310
|
+
return parseInt(match[1], 10) >= 3
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
main().catch((err) => {
|
|
314
|
+
signale.error('启动失败:', err)
|
|
315
|
+
process.exit(1)
|
|
316
|
+
})
|
package/lib/entry-package.js
CHANGED
|
@@ -1,16 +1,74 @@
|
|
|
1
|
-
// @quicktvui/web-cli - web entry
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// @quicktvui/web-cli - web entry
|
|
2
|
+
// 通过 @quicktvui/web-renderer 导入所有依赖
|
|
3
|
+
// 动态加载主入口,确保初始化顺序正确
|
|
4
4
|
|
|
5
5
|
console.log('[Web Renderer] === Starting initialization ===')
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
initAsyncLocalStorage,
|
|
9
|
+
initAutoProxy,
|
|
10
|
+
setupSceneBuilder,
|
|
11
|
+
createWebEngine,
|
|
12
|
+
applyAllPatches,
|
|
13
|
+
TVFocusManager,
|
|
14
|
+
startWebEngine,
|
|
15
|
+
APP_NAME,
|
|
16
|
+
IJKPlayerComponent,
|
|
17
|
+
} from '@quicktvui/web-renderer'
|
|
8
18
|
|
|
9
|
-
|
|
19
|
+
// 初始化 async localStorage
|
|
20
|
+
initAsyncLocalStorage()
|
|
10
21
|
|
|
22
|
+
// 初始化自动代理
|
|
23
|
+
initAutoProxy()
|
|
24
|
+
|
|
25
|
+
// 注册 IJKPlayerComponent
|
|
26
|
+
global.__WEB_COMPONENTS__ = global.__WEB_COMPONENTS__ || {}
|
|
27
|
+
global.__WEB_COMPONENTS__['IJKPlayerComponent'] = IJKPlayerComponent
|
|
28
|
+
|
|
29
|
+
// 设置 SceneBuilder
|
|
30
|
+
setupSceneBuilder()
|
|
31
|
+
|
|
32
|
+
// 创建 Web 引擎
|
|
33
|
+
const engine = createWebEngine()
|
|
34
|
+
|
|
35
|
+
// 应用所有补丁
|
|
36
|
+
applyAllPatches(engine)
|
|
37
|
+
|
|
38
|
+
// 初始化 TV 焦点管理器
|
|
39
|
+
global.__TV_FOCUS_MANAGER__ = new TVFocusManager()
|
|
40
|
+
console.log('[Web Renderer] TVFocusManager initialized')
|
|
41
|
+
|
|
42
|
+
// 注入全局 CSS
|
|
43
|
+
const styleEl = document.createElement('style')
|
|
44
|
+
styleEl.id = 'web-platform-reset'
|
|
45
|
+
styleEl.textContent = `
|
|
46
|
+
#app, #app * { align-items: flex-start; }
|
|
47
|
+
[style*="align-items"] { align-items: var(--align-items, center) !important; }
|
|
48
|
+
`
|
|
49
|
+
document.head.appendChild(styleEl)
|
|
50
|
+
console.log('[Web Renderer] Global CSS reset injected')
|
|
51
|
+
|
|
52
|
+
// 动态加载主入口,等待加载完成后再启动引擎
|
|
11
53
|
console.log('[Web Renderer] Importing main module...')
|
|
12
54
|
import(__QUICKTVUI_MAIN_ENTRY__)
|
|
55
|
+
.then(() => {
|
|
56
|
+
console.log('[Web Renderer] Main module loaded')
|
|
57
|
+
console.log('[Web Renderer] Starting engine...')
|
|
58
|
+
startWebEngine(engine, APP_NAME)
|
|
59
|
+
setTimeout(() => {
|
|
60
|
+
console.log('[Web Renderer] === Initialization complete ===')
|
|
61
|
+
}, 1000)
|
|
62
|
+
})
|
|
63
|
+
.catch((err) => {
|
|
64
|
+
console.error('[Web Renderer] Failed to load main module:', err)
|
|
65
|
+
})
|
|
13
66
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
67
|
+
// HMR 支持
|
|
68
|
+
if (module.hot) {
|
|
69
|
+
module.hot.accept()
|
|
70
|
+
module.hot.accept(__QUICKTVUI_MAIN_ENTRY__, () => {
|
|
71
|
+
console.log('[Web Renderer] Main module updated, reloading...')
|
|
72
|
+
window.location.reload()
|
|
73
|
+
})
|
|
74
|
+
}
|
package/lib/webpack.config.js
CHANGED
|
@@ -9,7 +9,6 @@ const fs = require('fs')
|
|
|
9
9
|
// 从环境变量获取配置
|
|
10
10
|
const projectRoot = process.env.QUICKTVUI_PROJECT_ROOT || process.cwd()
|
|
11
11
|
const mainEntry = process.env.QUICKTVUI_MAIN_ENTRY || path.resolve(projectRoot, 'src/main.ts')
|
|
12
|
-
const entryType = process.env.QUICKTVUI_ENTRY_TYPE || 'package'
|
|
13
12
|
const port = parseInt(process.env.QUICKTVUI_PORT || '39001', 10)
|
|
14
13
|
|
|
15
14
|
// 尝试加载项目的 package.json
|
|
@@ -67,6 +66,20 @@ function detectModules() {
|
|
|
67
66
|
if (fs.existsSync(cwdNodeModules) && !modules.includes(cwdNodeModules)) {
|
|
68
67
|
modules.push(cwdNodeModules)
|
|
69
68
|
}
|
|
69
|
+
// 添加 web-cli 自身的 node_modules 路径(支持全局安装)
|
|
70
|
+
const cliNodeModules = path.resolve(__dirname, '../node_modules')
|
|
71
|
+
if (fs.existsSync(cliNodeModules) && !modules.includes(cliNodeModules)) {
|
|
72
|
+
modules.push(cliNodeModules)
|
|
73
|
+
}
|
|
74
|
+
// 添加全局 node_modules 路径
|
|
75
|
+
try {
|
|
76
|
+
const globalNodeModules = require('child_process')
|
|
77
|
+
.execSync('npm root -g', { encoding: 'utf-8' })
|
|
78
|
+
.trim()
|
|
79
|
+
if (fs.existsSync(globalNodeModules) && !modules.includes(globalNodeModules)) {
|
|
80
|
+
modules.push(globalNodeModules)
|
|
81
|
+
}
|
|
82
|
+
} catch (e) {}
|
|
70
83
|
return modules
|
|
71
84
|
}
|
|
72
85
|
|
|
@@ -87,30 +100,21 @@ function detectCSSLoader() {
|
|
|
87
100
|
|
|
88
101
|
const cssLoader = detectCSSLoader()
|
|
89
102
|
|
|
90
|
-
//
|
|
91
|
-
|
|
103
|
+
// 获取 web-cli 目录下的 loader 路径
|
|
104
|
+
const cliDir = path.resolve(__dirname, '..')
|
|
105
|
+
const resolveLoader = (loaderName) => require.resolve(loaderName, { paths: [cliDir] })
|
|
92
106
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
} else if (entryType === 'local') {
|
|
97
|
-
// 使用 entry-local.js + main 入口
|
|
98
|
-
const entryLocalFile = path.resolve(__dirname, 'entry-local.js')
|
|
99
|
-
entryFiles = ['regenerator-runtime/runtime', entryLocalFile, mainEntry]
|
|
100
|
-
} else {
|
|
101
|
-
// 使用 entry-package.js + main 入口
|
|
102
|
-
const entryPackageFile = path.resolve(__dirname, 'entry-package.js')
|
|
103
|
-
entryFiles = ['regenerator-runtime/runtime', entryPackageFile, mainEntry]
|
|
104
|
-
}
|
|
107
|
+
// 入口文件:只加载 entry-package.js,主入口通过动态 import 加载
|
|
108
|
+
const entryPackageFile = path.resolve(__dirname, 'entry-package.js')
|
|
109
|
+
const entryFiles = ['regenerator-runtime/runtime', entryPackageFile]
|
|
105
110
|
|
|
106
111
|
module.exports = {
|
|
107
112
|
mode: 'development',
|
|
108
|
-
bail:
|
|
113
|
+
bail: false,
|
|
109
114
|
|
|
110
115
|
devServer: {
|
|
111
116
|
port,
|
|
112
117
|
hot: true,
|
|
113
|
-
liveReload: true,
|
|
114
118
|
proxy: [
|
|
115
119
|
{
|
|
116
120
|
context: ['/proxy'],
|
|
@@ -138,10 +142,6 @@ module.exports = {
|
|
|
138
142
|
},
|
|
139
143
|
},
|
|
140
144
|
],
|
|
141
|
-
static: {
|
|
142
|
-
directory: path.join(projectRoot, 'public'),
|
|
143
|
-
publicPath: '/',
|
|
144
|
-
},
|
|
145
145
|
client: {
|
|
146
146
|
overlay: { errors: true, warnings: false },
|
|
147
147
|
},
|
|
@@ -168,6 +168,7 @@ module.exports = {
|
|
|
168
168
|
},
|
|
169
169
|
|
|
170
170
|
plugins: [
|
|
171
|
+
new (require('webpack').HotModuleReplacementPlugin)(),
|
|
171
172
|
new (require('vue-loader').VueLoaderPlugin)(),
|
|
172
173
|
new (require('html-webpack-plugin'))({
|
|
173
174
|
inject: true,
|
|
@@ -175,28 +176,10 @@ module.exports = {
|
|
|
175
176
|
template: path.resolve(__dirname, '../templates/web-renderer.html'),
|
|
176
177
|
title: pkg.name || 'QuickTVUI Web',
|
|
177
178
|
}),
|
|
178
|
-
//
|
|
179
|
+
// 注入主入口路径和环境变量
|
|
179
180
|
new (require('webpack').DefinePlugin)({
|
|
180
181
|
__QUICKTVUI_PROJECT_ROOT__: JSON.stringify(projectRoot),
|
|
181
182
|
__QUICKTVUI_MAIN_ENTRY__: JSON.stringify(mainEntry),
|
|
182
|
-
// 预计算 src/web 模块路径(浏览器环境无法使用 path 模块)
|
|
183
|
-
__QUICKTVUI_MODULE_ASYNC_LOCAL_STORAGE__: JSON.stringify(
|
|
184
|
-
path.join(projectRoot, 'src/web/core/asyncLocalStorage')
|
|
185
|
-
),
|
|
186
|
-
__QUICKTVUI_MODULE_AUTO_PROXY__: JSON.stringify(
|
|
187
|
-
path.join(projectRoot, 'src/web/core/autoProxy')
|
|
188
|
-
),
|
|
189
|
-
__QUICKTVUI_MODULE_SCENE_BUILDER__: JSON.stringify(
|
|
190
|
-
path.join(projectRoot, 'src/web/core/SceneBuilder')
|
|
191
|
-
),
|
|
192
|
-
__QUICKTVUI_MODULE_WEB_ENGINE__: JSON.stringify(path.join(projectRoot, 'src/web')),
|
|
193
|
-
__QUICKTVUI_MODULE_PATCHES__: JSON.stringify(path.join(projectRoot, 'src/web/core/patches')),
|
|
194
|
-
__QUICKTVUI_MODULE_TV_FOCUS_MANAGER__: JSON.stringify(
|
|
195
|
-
path.join(projectRoot, 'src/web/core/TVFocusManager')
|
|
196
|
-
),
|
|
197
|
-
__QUICKTVUI_MODULE_PAGE_LIFECYCLE__: JSON.stringify(
|
|
198
|
-
path.join(projectRoot, 'src/web/core/pageLifecycle')
|
|
199
|
-
),
|
|
200
183
|
process: JSON.stringify({
|
|
201
184
|
env: { NODE_ENV: 'development' },
|
|
202
185
|
browser: true,
|
|
@@ -218,7 +201,7 @@ module.exports = {
|
|
|
218
201
|
loader: 'vue-loader',
|
|
219
202
|
options: { compilerOptions: { whitespace: 'condense' } },
|
|
220
203
|
},
|
|
221
|
-
'scope-loader',
|
|
204
|
+
require.resolve('scope-loader', { paths: [path.resolve(__dirname, '..')] }),
|
|
222
205
|
],
|
|
223
206
|
},
|
|
224
207
|
// TypeScript 文件
|
|
@@ -298,9 +281,4 @@ module.exports = {
|
|
|
298
281
|
|
|
299
282
|
// 忽略 entry-local.js 中动态 require 的警告(这是预期行为)
|
|
300
283
|
ignoreWarnings: [/Critical dependency: the request of a dependency is an expression/],
|
|
301
|
-
|
|
302
|
-
cache: {
|
|
303
|
-
type: 'filesystem',
|
|
304
|
-
buildDependencies: { config: [__filename] },
|
|
305
|
-
},
|
|
306
284
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quicktvui/web-cli",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.31",
|
|
4
4
|
"description": "CLI tool for QuickTVUI web development - zero configuration",
|
|
5
5
|
"author": "QuickTVUI Team",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"bin": {
|
|
8
|
-
"qt-web-
|
|
8
|
+
"qt-web-cli": "./bin/qt-web-cli.js"
|
|
9
9
|
},
|
|
10
10
|
"main": "lib/index.js",
|
|
11
11
|
"files": [
|
|
@@ -24,27 +24,33 @@
|
|
|
24
24
|
"minimist": "^1.2.8",
|
|
25
25
|
"shelljs": "^0.10.0",
|
|
26
26
|
"signale": "^1.4.0",
|
|
27
|
-
"@quicktvui/web-renderer": "^1.0.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"webpack": "^5.
|
|
31
|
-
"webpack-
|
|
32
|
-
"webpack-dev-server": "^4.0.0",
|
|
27
|
+
"@quicktvui/web-renderer": "^1.0.9",
|
|
28
|
+
"scope-loader": "^1.0.3",
|
|
29
|
+
"webpack": "^5.89.0",
|
|
30
|
+
"webpack-cli": "^5.1.0",
|
|
31
|
+
"webpack-dev-server": "^4.15.0",
|
|
33
32
|
"vue-loader": "^17.0.0",
|
|
34
|
-
"html-webpack-plugin": "^5.
|
|
35
|
-
"@babel/core": "^7.
|
|
36
|
-
"babel-loader": "^9.
|
|
33
|
+
"html-webpack-plugin": "^5.5.0",
|
|
34
|
+
"@babel/core": "^7.23.0",
|
|
35
|
+
"babel-loader": "^9.1.0",
|
|
37
36
|
"ts-loader": "^9.4.0",
|
|
38
|
-
"
|
|
39
|
-
"@extscreen/es3-router": ">=3.0.1"
|
|
37
|
+
"regenerator-runtime": "^0.14.0"
|
|
40
38
|
},
|
|
41
39
|
"peerDependenciesMeta": {
|
|
42
|
-
"less-loader": {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
40
|
+
"less-loader": {
|
|
41
|
+
"optional": true
|
|
42
|
+
},
|
|
43
|
+
"sass-loader": {
|
|
44
|
+
"optional": true
|
|
45
|
+
},
|
|
46
|
+
"sass": {
|
|
47
|
+
"optional": true
|
|
48
|
+
},
|
|
49
|
+
"less": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
46
52
|
},
|
|
47
53
|
"engines": {
|
|
48
54
|
"node": ">=16.0.0"
|
|
49
55
|
}
|
|
50
|
-
}
|
|
56
|
+
}
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
height: 1080px !important;
|
|
51
51
|
transform-origin: top left;
|
|
52
52
|
background-color: #26292F;
|
|
53
|
+
visibility: hidden;
|
|
53
54
|
}
|
|
54
55
|
/* 返回按钮样式 */
|
|
55
56
|
#web-back-btn {
|
|
@@ -131,6 +132,9 @@
|
|
|
131
132
|
var offsetY = (_originalInnerHeight - TV_HEIGHT * scale) / 2;
|
|
132
133
|
app.style.marginLeft = offsetX + 'px';
|
|
133
134
|
app.style.marginTop = offsetY + 'px';
|
|
135
|
+
|
|
136
|
+
// Show the app after positioning
|
|
137
|
+
app.style.visibility = 'visible';
|
|
134
138
|
|
|
135
139
|
// 更新返回按钮位置,相对于 #app 左上角
|
|
136
140
|
var btn = document.getElementById('web-back-btn');
|
|
@@ -144,6 +148,13 @@
|
|
|
144
148
|
}
|
|
145
149
|
}
|
|
146
150
|
}
|
|
151
|
+
|
|
152
|
+
// Execute immediately when DOM is ready (before load event)
|
|
153
|
+
if (document.readyState === 'loading') {
|
|
154
|
+
document.addEventListener('DOMContentLoaded', scaleApp);
|
|
155
|
+
} else {
|
|
156
|
+
scaleApp();
|
|
157
|
+
}
|
|
147
158
|
window.addEventListener('load', scaleApp);
|
|
148
159
|
window.addEventListener('resize', scaleApp);
|
|
149
160
|
</script>
|
package/bin/qt-web-dev.js
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* QuickTVUI Web Dev CLI
|
|
5
|
-
* 启动 web 开发服务器,无需手动配置入口文件
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const path = require('path')
|
|
9
|
-
const fs = require('fs')
|
|
10
|
-
const signale = require('signale')
|
|
11
|
-
const minimist = require('minimist')
|
|
12
|
-
const { exec } = require('shelljs')
|
|
13
|
-
|
|
14
|
-
// 解析命令行参数
|
|
15
|
-
const args = minimist(process.argv.slice(2), {
|
|
16
|
-
string: ['port', 'config'],
|
|
17
|
-
boolean: ['help', 'open'],
|
|
18
|
-
alias: {
|
|
19
|
-
p: 'port',
|
|
20
|
-
c: 'config',
|
|
21
|
-
h: 'help',
|
|
22
|
-
o: 'open',
|
|
23
|
-
},
|
|
24
|
-
default: {
|
|
25
|
-
port: 39001,
|
|
26
|
-
open: false,
|
|
27
|
-
},
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
// 显示帮助信息
|
|
31
|
-
if (args.help) {
|
|
32
|
-
console.log(`
|
|
33
|
-
QuickTVUI Web Dev CLI
|
|
34
|
-
|
|
35
|
-
Usage:
|
|
36
|
-
qt-web-dev [options]
|
|
37
|
-
|
|
38
|
-
Options:
|
|
39
|
-
-p, --port <port> 开发服务器端口 (默认: 39001)
|
|
40
|
-
-c, --config <path> 自定义 webpack 配置文件路径
|
|
41
|
-
-o, --open 自动打开浏览器
|
|
42
|
-
-h, --help 显示帮助信息
|
|
43
|
-
|
|
44
|
-
Examples:
|
|
45
|
-
qt-web-dev
|
|
46
|
-
qt-web-dev --port 8080
|
|
47
|
-
qt-web-dev --config ./my.webpack.js
|
|
48
|
-
|
|
49
|
-
特点:
|
|
50
|
-
- 自动检测项目入口,优先使用 webMain
|
|
51
|
-
- 若无 webMain,自动使用 src/web 或 @quicktvui/web-renderer 包
|
|
52
|
-
- 零配置启动 web 开发服务器
|
|
53
|
-
`)
|
|
54
|
-
process.exit(0)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async function main() {
|
|
58
|
-
signale.pending('正在启动 QuickTVUI Web 开发服务器...')
|
|
59
|
-
|
|
60
|
-
// 查找项目根目录
|
|
61
|
-
const projectRoot = findProjectRoot()
|
|
62
|
-
if (!projectRoot) {
|
|
63
|
-
signale.error('无法找到项目根目录')
|
|
64
|
-
process.exit(1)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
signale.info(`项目根目录: ${projectRoot}`)
|
|
68
|
-
|
|
69
|
-
// 读取 package.json
|
|
70
|
-
let pkg = {}
|
|
71
|
-
try {
|
|
72
|
-
pkg = require(path.join(projectRoot, 'package.json'))
|
|
73
|
-
} catch (e) {}
|
|
74
|
-
|
|
75
|
-
// 检测入口文件
|
|
76
|
-
let mainEntry
|
|
77
|
-
let entryType
|
|
78
|
-
|
|
79
|
-
if (pkg.webMain) {
|
|
80
|
-
// 项目已有 webMain,直接使用
|
|
81
|
-
mainEntry = pkg.webMain
|
|
82
|
-
entryType = 'webmain'
|
|
83
|
-
signale.info(`主入口: ${mainEntry} (webMain)`)
|
|
84
|
-
} else if (fs.existsSync(path.join(projectRoot, 'src/web/index.js'))) {
|
|
85
|
-
// 有 src/web 目录,使用 entry-local.js 包装 main
|
|
86
|
-
mainEntry = pkg.main || './src/main.ts'
|
|
87
|
-
entryType = 'local'
|
|
88
|
-
signale.info(`主入口: ${mainEntry}`)
|
|
89
|
-
signale.info(`Web 渲染器: src/web (本地)`)
|
|
90
|
-
} else {
|
|
91
|
-
// 使用 @quicktvui/web-renderer 包
|
|
92
|
-
mainEntry = pkg.main || './src/main.ts'
|
|
93
|
-
entryType = 'package'
|
|
94
|
-
signale.info(`主入口: ${mainEntry}`)
|
|
95
|
-
signale.info(`Web 渲染器: @quicktvui/web-renderer (包)`)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const mainEntryPath = path.resolve(projectRoot, mainEntry)
|
|
99
|
-
|
|
100
|
-
// 获取 webpack 配置
|
|
101
|
-
let webpackConfigPath
|
|
102
|
-
if (args.config) {
|
|
103
|
-
webpackConfigPath = path.resolve(projectRoot, args.config)
|
|
104
|
-
} else {
|
|
105
|
-
webpackConfigPath = path.resolve(__dirname, '../lib/webpack.config.js')
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// 设置环境变量
|
|
109
|
-
process.env.NODE_ENV = 'development'
|
|
110
|
-
process.env.QUICKTVUI_PROJECT_ROOT = projectRoot
|
|
111
|
-
process.env.QUICKTVUI_MAIN_ENTRY = mainEntryPath
|
|
112
|
-
process.env.QUICKTVUI_ENTRY_TYPE = entryType
|
|
113
|
-
process.env.QUICKTVUI_PORT = String(args.port)
|
|
114
|
-
|
|
115
|
-
// 检测 OpenSSL 版本
|
|
116
|
-
const needLegacyProvider = checkOpenSSLVersion(process.versions.openssl)
|
|
117
|
-
let nodeOptions = ''
|
|
118
|
-
if (needLegacyProvider) {
|
|
119
|
-
nodeOptions = '--openssl-legacy-provider'
|
|
120
|
-
signale.warn(`检测到 OpenSSL ${process.versions.openssl},已添加 --openssl-legacy-provider`)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// 构建 webpack 命令
|
|
124
|
-
const webpackArgs = ['serve', `--config "${webpackConfigPath}"`, `--port ${args.port}`, '--color']
|
|
125
|
-
if (args.open) webpackArgs.push('--open')
|
|
126
|
-
|
|
127
|
-
const envPrefix = nodeOptions ? `NODE_OPTIONS="${nodeOptions}" ` : ''
|
|
128
|
-
const webpackCmd = `${envPrefix}npx webpack ${webpackArgs.join(' ')}`
|
|
129
|
-
|
|
130
|
-
signale.pending(`执行: ${webpackCmd}`)
|
|
131
|
-
|
|
132
|
-
const result = exec(webpackCmd, { stdio: 'inherit' })
|
|
133
|
-
if (result.code !== 0) {
|
|
134
|
-
signale.error('启动开发服务器失败')
|
|
135
|
-
process.exit(1)
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function findProjectRoot(startDir = process.cwd()) {
|
|
140
|
-
let currentDir = startDir
|
|
141
|
-
while (currentDir !== path.dirname(currentDir)) {
|
|
142
|
-
if (fs.existsSync(path.join(currentDir, 'package.json'))) {
|
|
143
|
-
return currentDir
|
|
144
|
-
}
|
|
145
|
-
currentDir = path.dirname(currentDir)
|
|
146
|
-
}
|
|
147
|
-
return null
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function checkOpenSSLVersion(version) {
|
|
151
|
-
if (!version) return false
|
|
152
|
-
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version)
|
|
153
|
-
if (!match) return false
|
|
154
|
-
return parseInt(match[1], 10) >= 3
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
main().catch((err) => {
|
|
158
|
-
signale.error('启动失败:', err)
|
|
159
|
-
process.exit(1)
|
|
160
|
-
})
|