diff --git a/app/Http/Controllers/IndexController.php b/app/Http/Controllers/IndexController.php
index 6a7be2c74..1d7b589dc 100755
--- a/app/Http/Controllers/IndexController.php
+++ b/app/Http/Controllers/IndexController.php
@@ -63,11 +63,8 @@ class IndexController extends InvokeController
if (file_exists($hotFile)) {
$array = Base::json2array(file_get_contents($hotFile));
$style = null;
- $script = preg_replace("/^(\/\/(.*?))(:\d+)?\//i", "$1:" . $array['APP_DEV_PORT'] . "/", asset_main("resources/assets/js/app.js"));
- $proxyUri = Base::liveEnv('VSCODE_PROXY_URI');
- if (is_string($proxyUri) && preg_match('/^https?:\/\//i', $proxyUri)) {
- $script = preg_replace('/^(https?:\/\/|\/\/)[^\/]+/', rtrim($proxyUri, '/'), $script, 1);
- }
+ // 开发模式:vite 经 nginx 反代在同源的 base 路径下(见 vite.config.js devProxyPlugin)
+ $script = asset_main(trim($array['base'] ?? '/__vite/', '/') . "/resources/assets/js/app.js");
} else {
$array = Base::json2array(file_get_contents($manifestFile));
$style = asset_main($array['resources/assets/js/app.js']['css'][0]);
diff --git a/cmd b/cmd
index 85d535449..7432e8997 100755
--- a/cmd
+++ b/cmd
@@ -379,18 +379,6 @@ web_build() {
if [ ! -d "./node_modules" ]; then
npm install
fi
- if [ "$type" = "dev" ]; then
- echo "" > ./index.html
- if [ -z "$(env_get APP_DEV_PORT)" ]; then
- env_set APP_DEV_PORT $(rand 20001 30000)
- fi
- if [ -n "${VSCODE_PROXY_URI:-}" ]; then
- APP_REAL_URI=$(TARGET_PORT="$(env_get APP_PORT)" node -p "process.env.VSCODE_PROXY_URI.replace(/\{\{port\}\}/g, process.env.TARGET_PORT || '')")
- VSCODE_PROXY_URI=$(APP_DEV_PORT="$(env_get APP_DEV_PORT)" node -p "process.env.VSCODE_PROXY_URI.replace(/\{\{port\}\}/g, process.env.APP_DEV_PORT || '')")
- echo "" > ./index.html
- fi
- env_set VSCODE_PROXY_URI "${VSCODE_PROXY_URI:-}"
- fi
switch_debug "$type"
#
if [ "$type" = "prod" ]; then
diff --git a/docker-compose.yml b/docker-compose.yml
index c735a6d0b..5a3084d12 100755
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -45,6 +45,8 @@ services:
- "${APP_SSL_PORT:-0}:443"
environment:
APP_SCHEME: "${APP_SCHEME:-auto}"
+ extra_hosts:
+ - "host.docker.internal:host-gateway"
volumes:
- ./docker/nginx/default.conf:/etc/nginx/templates/default.conf.template
- ./:/var/www
diff --git a/vite.config.js b/vite.config.js
index a89b2b2c3..06dc20225 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -18,28 +18,99 @@ if (!argv.includes('fromcmd')) {
process.exit()
}
+/**
+ * 开发模式:由 nginx 把 /__vite/ 反代到本机 vite,页面与 vite 同源,任意访问地址/代理下均可用
+ * - 监听成功后写入 nginx 反代片段并 reload,同时写 public/hot 通知后端走开发资源
+ * - 退出时清理并 reload
+ */
+function devProxyPlugin(env, devBase) {
+ const hotFile = path.resolve(__dirname, 'public/hot')
+ const confFile = path.resolve(__dirname, 'docker/nginx/site/vite-dev.conf')
+ const nginxName = `dootask-nginx-${env['APP_ID']}`
+ const reloadNginx = () => {
+ try {
+ // nginx -s reload 在配置有误时也返回成功,先 -t 校验
+ execSync(`docker exec ${nginxName} sh -c "nginx -t -q && nginx -s reload"`, {stdio: 'pipe'})
+ return true
+ } catch (e) {
+ console.warn(`[dev-proxy] reload ${nginxName} failed: ${String(e.stderr || e.message).trim()}`)
+ return false
+ }
+ }
+ const cleanup = () => {
+ const exists = fs.existsSync(confFile)
+ fs.rmSync(hotFile, {force: true})
+ fs.rmSync(confFile, {force: true})
+ exists && reloadNginx()
+ }
+ return {
+ name: 'dootask-dev-proxy',
+ configureServer(server) {
+ // 浏览器经 nginx 访问应用,不直接访问 vite,替换 vite 默认打印的地址
+ server.printUrls = () => {
+ const {info, warn} = server.config.logger
+ if (!fs.existsSync(hotFile)) {
+ warn(` > Proxy unavailable`)
+ return
+ }
+ // 与 vite 默认输出一致:地址青色、端口加粗
+ const useColor = process.stdout.isTTY && !('NO_COLOR' in process.env)
+ const ansi = (open, close) => text => useColor ? `\x1b[${open}m${text}\x1b[${close}m` : text
+ const cyan = ansi(36, 39), bold = ansi(1, 22)
+ const appPort = env['APP_PORT']
+ info(` > Local: ${cyan(`http://localhost:${bold(appPort)}/`)}`)
+ const proxyUri = process.env['VSCODE_PROXY_URI']
+ if (proxyUri && proxyUri.includes('{{port}}')) {
+ info(` > Network: ${cyan(proxyUri.replace(/\{\{port\}\}/g, bold(appPort)))}`)
+ }
+ }
+ server.httpServer.once('listening', () => {
+ const {port} = server.httpServer.address()
+ fs.writeFileSync(confFile, [
+ `location ^~ ${devBase} {`,
+ ` proxy_http_version 1.1;`,
+ ` proxy_set_header Host $host;`,
+ ` proxy_set_header Upgrade $http_upgrade;`,
+ ` proxy_set_header Connection $connection_upgrade;`,
+ ` proxy_read_timeout 1d;`,
+ ` proxy_buffering off;`,
+ ` proxy_pass http://host.docker.internal:${port};`,
+ `}`,
+ ``,
+ ].join('\n'))
+ if (reloadNginx()) {
+ fs.writeFileSync(hotFile, JSON.stringify({base: devBase, port}))
+ } else {
+ // 不保留无效片段,否则 nginx 容器重启时会因配置错误起不来
+ fs.rmSync(confFile, {force: true})
+ }
+ })
+ // 修改配置后 vite 会重新加载本文件并重启服务,退出钩子挂在 globalThis 上只注册一次
+ if (!globalThis.__dootaskDevProxyHooked) {
+ globalThis.__dootaskDevProxyHooked = true
+ process.on('exit', cleanup)
+ for (const signal of ['SIGINT', 'SIGHUP', 'SIGTERM']) {
+ process.on(signal, () => process.exit())
+ }
+ }
+ },
+ transform(code, id) {
+ // vite 2 的 HMR 客户端固定连 location.hostname:端口,改为连当前页面的源(经 nginx 转发)
+ if (id.includes('/vite/dist/client/client.mjs')) {
+ return code.replace(
+ /const socketHost = `\$\{__HMR_HOSTNAME__ \|\| location\.hostname\}:\$\{__HMR_PORT__\}`;/,
+ `const socketHost = location.host + ${JSON.stringify(devBase)};`
+ )
+ }
+ },
+ }
+}
+
export default defineConfig(({command, mode}) => {
const env = loadEnv(mode, process.cwd(), '')
const host = "0.0.0.0"
- const port = parseInt(env['APP_DEV_PORT'])
- const proxy_uri = env['VSCODE_PROXY_URI']
-
- if (command === 'serve') {
- const hotFile = path.resolve(__dirname, 'public/hot')
- const hotClean = (exit) => {
- if (fs.existsSync(hotFile)) {
- fs.unlinkSync(hotFile);
- }
- if (exit) {
- process.exit()
- }
- }
- hotClean(false)
- fs.writeFileSync(hotFile, JSON.stringify(env));
- process.on('exit', () => hotClean(true));
- process.on('SIGINT', () => hotClean(true));
- process.on('SIGHUP', () => hotClean(true));
- }
+ const port = parseInt(env['APP_DEV_PORT']) || 5173
+ const devBase = '/__vite/'
const plugins = [
createVuePlugin({
@@ -52,6 +123,9 @@ export default defineConfig(({command, mode}) => {
vitePluginRequire(),
vitePluginFileCopy([staticDir])
]
+ if (command === 'serve') {
+ plugins.push(devProxyPlugin(env, devBase))
+ }
if (mode === "development") {
plugins.push({
name: 'watch-copy',
@@ -81,24 +155,13 @@ export default defineConfig(({command, mode}) => {
})
}
- const serverHmr = {}
- if (/^https?:\/\//i.test(proxy_uri)) {
- const proxyUri = new URL(proxy_uri)
- if (proxyUri) {
- Object.assign(serverHmr, {
- host: proxyUri.host,
- clientPort: proxyUri.port || (/^https/.test(proxy_uri) ? 443 : 80)
- })
- }
- }
-
return {
- base: basePath,
+ base: command === 'serve' ? devBase : basePath,
publicDir: publicPath,
server: {
host,
port,
- strictPort: false,
+ strictPort: false, // 端口被占用时自动顺延,实际端口由 devProxyPlugin 同步给 nginx
watch: {
ignored: [
'**/node_modules/**',
@@ -111,7 +174,6 @@ export default defineConfig(({command, mode}) => {
'**/electron/**',
]
},
- hmr: serverHmr
},
resolve: {
alias: {