PAYATHON 2026

Windows 上 Angular ng serve 无法读取 symlink 中的 assets

排障赵师傅

结论

在 angular.json 的 assets 配置中显式启用 followSymlinks。preserveSymlinks 影响的是模块解析,不会让 assets 文件匹配器递归遍历符号链接目录。只设置 preserveSymlinks,无法解决子目录文件返回 404 的问题。

将原来的字符串配置:

"assets": [
  "src/favicon.ico",
  "src/assets"
]

改为对象配置:

"assets": [
  "src/favicon.ico",
  {
    "glob": "**/*",
    "input": "src/assets",
    "output": "assets",
    "followSymlinks": true
  }
]

修改后,完全停止并重新执行 ng serve。

为什么顶层文件可以读取,子目录却返回 404

ng serve 不会直接把磁盘上的 src/assets 作为静态目录提供给浏览器。Angular CLI 会按照 angular.json 中的 assets 规则匹配文件,再把匹配到的内容复制到构建输出或开发服务器使用的内存文件系统。

assets 匹配默认不会继续遍历符号链接指向的目录,因此可能出现以下情况:

  • src/assets/app 入口可以被识别;
  • 链接目录顶层的个别文件可能被匹配;
  • 链接目标中的子目录没有被递归遍历;
  • 浏览器请求 /assets/app/subdir/data.json 时,开发服务器中没有对应文件,最终返回 404。

浏览器控制台中的 URL 正确,只能说明前端发出了正确的请求,无法证明 Angular CLI 已经把该文件收集到 assets 输出中。

推荐配置

假设目录结构如下:

angular-project/
├── angular.json
├── src/
│   └── assets/
│       └── app -> ../../data-repository/generated-data
└── data-repository/
    └── generated-data/
        ├── config.json
        └── records/
            └── example.json

在项目的 build 配置中,将 assets 规则写成对象形式:

{
  "projects": {
    "info-mapper": {
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/info-mapper",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "preserveSymlinks": true,
            "assets": [
              "src/favicon.ico",
              {
                "glob": "**/*",
                "input": "src/assets",
                "output": "assets",
                "followSymlinks": true
              }
            ]
          }
        }
      }
    }
  }
}

各字段的作用如下:

  • glob: "**/*":递归匹配文件和子目录。
  • input: "src/assets":从该目录收集静态资源。
  • output: "assets":将文件映射到输出目录中的 assets。
  • followSymlinks: true:允许文件匹配器进入符号链接指向的目录。

保存配置后,重启开发服务器:

ng serve

文件可通过以下 URL 访问:

/assets/app/config.json
/assets/app/records/example.json

如果只需要让 app 目录跟随符号链接,可以拆分配置:

"assets": [
  "src/favicon.ico",
  {
    "glob": "**/*",
    "input": "src/assets",
    "output": "assets",
    "ignore": [
      "app/**"
    ]
  },
  {
    "glob": "**/*",
    "input": "src/assets/app",
    "output": "assets/app",
    "followSymlinks": true
  }
]

这样会把符号链接处理限制在 src/assets/app,防止其他 assets 链接被意外遍历。

preserveSymlinks 和 assets 遍历处理的是不同问题。

preserveSymlinks: true 控制构建工具解析 JavaScript、TypeScript 或依赖模块时是否保留符号链接路径。它常用于通过 npm link 等方式连接本地 library,例如防止依赖解析到真实路径后产生重复模块。

assets 文件使用另一套复制和 glob 匹配流程。是否进入符号链接目录,取决于 assets 规则中的 followSymlinks。

以下配置可以保留:

"preserveSymlinks": true

不过,当前 404 问题需要使用的是:

"followSymlinks": true

Windows 下还需要检查什么

如果添加 followSymlinks 后仍然无法访问文件,需要检查链接类型和目标路径。

在 Windows 命令提示符中创建目录符号链接时,使用:

mklink /D src\assets\app C:\path\to\data-repository\generated-data

也可以创建目录 junction:

mklink /J src\assets\app C:\path\to\data-repository\generated-data

然后用 Node.js 检查链接目标能否读取:

node -e "console.log(require('fs').readdirSync('src/assets/app'))"

接着检查子目录:

node -e "console.log(require('fs').readdirSync('src/assets/app/records'))"

如果 Node.js 无法读取这些路径,问题出在 Windows 链接、权限或链接类型,而非 Angular 配置。Cygwin 模拟的 symlink 不一定等同于 Windows 原生符号链接。由于实际运行的是 Windows 版 Node.js,最好使用 Node.js 和 Windows 命令提示符都能识别的原生 symlink 或 junction。

同时确认以下事项:

  • input 和 output 没有与其他 assets 规则重复映射;
  • 修改 angular.json 后已经重启 ng serve;
  • 请求路径使用 /assets/app/...,大小写和文件扩展名均正确;
  • 启动 ng serve 时,链接目标目录已经存在;
  • 运行 Node.js 的 Windows 用户拥有链接目标的读取权限。

对于 Angular CLI 7.3.5,当前安装包是否接受这项配置,应以项目本地 @angular-devkit/build-angular 的 schema 校验结果为准。如果启动时提示 followSymlinks 不是允许的属性,说明项目实际使用的 builder 版本不支持该字段。此时需要升级到支持该 assets 选项的 Angular CLI/build-angular 版本,或者使用自定义静态文件复制方案。

备注:内容仅供参考。