Erori de compilare TypeScript la construirea Angular cu SSR + pnpm + preserveSymlinks
Aveam o aplicație Angular care se construia fără probleme. Singurele ciudățenii ale acestei aplicații sunt că are preserveSymlinks: true în angular.json și o bibliotecă locală instalată pentru dezvoltare folosind protocolul link: în dependențele package.json.
Totul funcționa până am adăugat SSR urmând ghidul din documentație executând
ng add @angular/ssr
Apoi, ng build a eșuat cu mai multe erori TypeScript. Iată un extras din acestea (inclusiv câte unul din fiecare tip):
✘ [ERROR] TS2688: Cannot find type definition file for 'express-serve-static-core'. [plugin angular-compiler]
node_modules/@types/express/index.d.ts:8:22:
8 │ /// <reference types="express-serve-static-core" />
╵ ~~~~~~~~~~~~~~~~~~~~~~~~~
// ...
✘ [ERROR] TS2307: Cannot find module 'body-parser' or its corresponding type declarations. [plugin angular-compiler]
node_modules/@types/express/index.d.ts:11:28:
11 │ import * as bodyParser from "body-parser";
╵ ~~~~~~~~~~~~~
// ...
✘ [ERROR] TS7006: Parameter 'req' implicitly has an 'any' type. [plugin angular-compiler]
server.ts:28:19:
28 │ server.get('*', (req, res, next) => {
╵ ~~~
// ...
✘ [ERROR] TS2339: Property 'listen' does not exist on type 'Express'. [plugin angular-compiler]
server.ts:51:9:
51 │ server.listen(port, () => {
╵ ~~~~~~
// ...
✘ [ERROR] Could not resolve "debug"
node_modules/express/lib/view.js:15:20:
15 │ var debug = require('debug')('express:view');
╵ ~~~~~~~
You can mark the path "debug" as external to exclude it from the bundle, which will remove this error and leave the unresolved path in the bundle. You can also surround this "require" call with a try/catch block to handle this failure at run-time instead of bundle-time.
Software utilizat:
- Angular/Angular CLI/Angular SSR:
17.3.2(ultima versiune la momentul scrierii acestui text) - Manager de pachete: pnpm
8.15.6(cea mai recentă la momentul scrierii) - Typescript:
5.4.3(cea mai recentă compatibilă cu Angular în momentul scrierii acestuia)
Răspuns #1
Rezumat
Dacă folosiți pnpm + preserveSymlinks în opțiunile targetului de build din Angular CLI + SSR pentru o aplicație Angular, setați node-linker la hoisted adăugând într-un fișier .npmrc în repository (creați-l dacă nu există):
node-linker=hoisted
Apoi, elimină directorul node_modules și rulează din nou pnpm install. ng build funcționează 🎉
De asemenea, poți folosi
npmîn loc depnpmpentru a rezolva problema, dar vei pierdepdinpnpm😜
Încercând să reproduc problema
După ce am încercat să creez un exemplu de aplicație minimal reproducibil, am descoperit că crearea unei noi aplicații Angular cu SSR, pnpm ca manager de pachete se construia corespunzător:
pnpm dlx @angular/cli@17 new a17-ssr --ssr --package-manager=pnpm
pnpm run build
Așa că am decis să caut diferențele dintre ambele proiecte.
În cele din urmă am descoperit că aveam ceva diferit în angular.json. În interiorul opțiunilor pentru ținta de build (projects.{name}.architect.build.options): preserveSymlinks era setat la true
Dacă este dezactivat, aplicația se construiește corect.
Totuși, am nevoie de această opțiune pentru a lega o dependență/pachet NPM local. În acest fel, pot actualiza dependența local și să văd imediat schimbările în aplicație fără a fi nevoie să reinstalez dependența locală.
Am încercat să folosesc npm în loc de pnpm pentru a vedea dacă era o problemă legată de managerul de pachete. Am șters node_modules, am folosit npm install și aplicația s-a construit. Chiar și cu preserveSymlinks activat 🤔 Deci se pare că este ceva legat de instalările pnpm
Problema
După o scurtă căutare, am aflat că este de fapt legat de modul în care pnpm instalează pachetele. Dependențele sunt, de fapt, legate simbolic din node_modules într-un magazin virtual al pnpm la node_modules/.pnpm. Și asta strică procesul de build. Nu sunt sigur de ce exact, sincer să fiu.
Soluția
Pentru a remedia acest lucru, putem schimba modul în care sunt instalate pachetele setând configurația node-linker. Vezi mai sus în TL;DR pentru cum să faci asta.
Alte alternative
Protocol de fișier
Trecerea la utilizarea protocolului file: a rezolvat problema. De asemenea, preserveSymlinks nu mai era necesar în configurația workspace-ului angular.json. Acest protocol folosește hard-linkuri, astfel încât modificările aduse bibliotecii legate instalate se reflectă imediat în aplicație fără a fi nevoie să o instalezi din nou. Așa cum este menționat în documentația pnpm, funcționează mai bine cu rezolvarea peerDependencies.
pnp node-linker
Am încercat rapid strategia pnp node-linker cu symlink activat, dar compilarea eșuează din cauza dependențelor care nu sunt găsite. Poate dacă configurezi corect Plug'n'Play funcționează, dar nu am vrut să pierd timp cu asta
