@nativescript/vite: extensionless shared-workspace module requests return 404

未关闭
#11,570 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 3 天内回复

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
3-5 天
新手友好度
57/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
领域
devtools, tooling

调研方向

Start at resolveNsMTransformedModule and resolveFsAnchoredSpec in the NativeScript Vite package’s hmr/server/websocket-ns.js route. Trace how extension and platform candidates are resolved under project and workspace roots, including Vite filesystem access checks. Reproduce with the supplied workspace-fetch.mjs; done means the extensionless shared module request returns 200 and relevant regression checks pass.

由索引模型根据 Issue 内容生成。

描述

bug

@nativescript/vite 8.0.20 serves a workspace-shared TSX module when its device URL includes .tsx, but returns 404 for the extensionless URL. The HMR client uses extensionless script URLs for reimports, so a successful static import does not establish that the same shared module can be hot-reimported.

Verified with the unpatched production NativeScript HTTP route, Vite 8.3.0 and Node 26.12.0. No Coreframe or Octane plugins, device runtime, or images are involved. This is separate from the graph-identity collision in #11569.

Expected and observed

With a native project at <workspace>/native and a module at <workspace>/shared/src/screen.tsx:

Request Actual
/ns/m/shared/src/screen.tsx 200
/ns/m/shared/src/screen 404

Both should serve the same module. server.fs.allow explicitly includes the workspace. The project and temporary directory use real paths to avoid a symlink/filesystem-allow artifact.

Reproduction

Create these files in an empty directory, run pnpm install, then node workspace-fetch.mjs. The assertion intentionally fails after printing the status pair and writing workspace-fetch-result.json.

package.json:

{
  "name": "nativescript-hmr-identity-repro",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "packageManager": "[email protected]",
  "scripts": {
    "check": "node repro.mjs"
  },
  "devDependencies": {
    "@nativescript/vite": "8.0.20",
    "vite": "8.3.0"
  }
}

pnpm-workspace.yaml:

packages:
  - .
allowBuilds:
  esbuild: true
  "@parcel/watcher": true
minimumReleaseAgeExclude:
  - "@nativescript/[email protected]"

workspace-fetch.mjs:

import assert from 'node:assert/strict'
import { mkdtempSync, realpathSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'
import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { pathToFileURL } from 'node:url'
import { createServer } from 'vite'

const ns = pathToFileURL(createRequire(import.meta.url).resolve('@nativescript/vite'))
const { registerNsModuleServerRoute } = await import(new URL('./hmr/server/websocket-ns-m.js', ns))
const { setProjectFlavor } = await import(new URL('./helpers/flavor.js', ns))
setProjectFlavor('javascript')
const workspace = realpathSync(mkdtempSync(join(tmpdir(), 'ns-workspace-fetch-')))
const root = join(workspace, 'native')
mkdirSync(join(root, 'src'), { recursive: true })
mkdirSync(join(workspace, 'shared/src'), { recursive: true })
writeFileSync(join(root, 'package.json'), '{"type":"module"}')
writeFileSync(join(workspace, 'pnpm-workspace.yaml'), 'packages:\n  - native\n')
writeFileSync(join(workspace, 'shared/src/screen.tsx'), 'export default "baseline";')
const server = await createServer({
  root, configFile: false, logLevel: 'silent', optimizeDeps: { noDiscovery: true },
  server: { host: '127.0.0.1', port: 0, watch: null, fs: { allow: [workspace] } },
  plugins: [{ name: 'native-route-repro', configureServer(server) {
    registerNsModuleServerRoute(server, {
      verbose: false, appVirtualWithSlash: '/src/', sfcFileMap: new Map(), depFileMap: new Map(),
      getGraphVersion: () => 1, getStrategy: () => ({ flavor: 'javascript' }), depsBundle: null,
      sharedTransformRequest: (id) => server.transformRequest(id),
      ensureInitialGraphPopulationStarted() {}, upsertGraphModule() {},
    })
  } }],
})
try {
  await server.listen()
  const origin = `http://127.0.0.1:${server.httpServer.address().port}`
  const results = []
  for (const url of ['/ns/m/shared/src/screen.tsx', '/ns/m/shared/src/screen']) {
    const response = await fetch(origin + url)
    results.push({ url, status: response.status })
    await response.text()
  }
  console.log(JSON.stringify(results))
  writeFileSync(new URL('./workspace-fetch-result.json', import.meta.url), JSON.stringify(results, null, 2) + '\n')
  assert.ok(results.every(({ status }) => status === 200), 'HMR extensionless fetch must serve the same shared module as its static URL')
} finally {
  await server.close()
  rmSync(workspace, { recursive: true, force: true })
}

Source finding and requested fix

resolveNsMTransformedModule first tries extension/platform candidates as Vite app URLs. Those URLs do not address shared files outside the Vite root. Its later filesystem-root fallback calls resolveFsAnchoredSpec only with the original, extensionless spec; it does not probe the expanded candidates under the workspace root.

Please resolve the same extension/platform candidates under the project/workspace filesystem roots and preserve Vite filesystem access checks. A local exact-version patch doing that passes a real HTTP regression for native-host, shared-workspace and external-source modules, together with graph/hot-context identity checks. This report does not claim device HMR qualification or an upstream-main runtime test.

主要语言
TypeScript
星标
25.7k
派生
1.7k
平均合并
4 天 14 小时
30 天内合并 PR
53

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

NativeScript/NativeScript 的其他 Issue

查看 NativeScript/NativeScript 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。