Closed JonSilver closed 1 year ago
This is definitely on my to-do list to figure out how to do. Especially in a microservices environment, this is a very much needed feature. I'll ping you back here once I future this out.
Thanks @AlexPshul. I played around some more, and came up with a VSCode tasks.json
solution that works pretty well.
Given an nx
monorepo with the following projects:
api-library
- an @nx/js
libraryapi
- an @nxazure/func
Azure Function Appdemo
- an @nx/react
Vite app... and the following task breakdown:
nx watch
tsc -watch
The whole thing can then be started up with just a Ctrl-Shift-B
and everything will hot reload... ish. If you modify the library or the API you'll still have to refresh the demo app in the browser.
So the tasks.json
looks something like this:
{
"version": "2.0.0",
"tasks": [
{
"label": "Build Lib",
"detail": "Build Lib",
"type": "shell",
"command": "nx build api-library && npx -c 'nx watch --projects=api-library -- nx run api-library:build:development'",
"options": {
"cwd": "${workspaceFolder}"
},
"isBackground": true,
"problemMatcher": {
"fileLocation": "relative",
"pattern": {
"regexp": "error",
"file": 1,
"location": 2,
"severity": 3,
"code": 4,
"message": 5
},
"background": {
"activeOnStart": true,
"beginsPattern": "Compiling TypeScript|nx run",
"endsPattern": "Successfully"
}
},
"group": {
"kind": "build"
}
},
{
"label": "Build API",
"detail": "Build API",
"type": "typescript",
"tsconfig": "./packages/api/tsconfig.build.json",
"option": "watch",
"options": {
"cwd": "${workspaceFolder}/packages/api"
},
"isBackground": true,
"dependsOn": ["Build Lib"],
"problemMatcher": "$tsc-watch",
"group": {
"kind": "build"
}
},
{
"label": "Start Backend",
"detail": "Start API",
"type": "shell",
"command": "nx start api",
"options": {
"cwd": "${workspaceFolder}"
},
"dependsOn": ["Build API"],
"isBackground": true,
"problemMatcher": "$func-node-watch",
"group": {
"kind": "build"
}
},
{
"label": "Start Frontend",
"detail": "Start UI",
"type": "shell",
"command": "nx serve demo",
"options": {
"cwd": "${workspaceFolder}"
},
"dependsOn": ["Start Backend"],
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"fileLocation": "relative",
"pattern": {
"regexp": "error",
"file": 1,
"location": 2,
"severity": 3,
"code": 4,
"message": 5
},
"background": {
"activeOnStart": true,
"beginsPattern": "nx run demo",
"endsPattern": "Local:"
}
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
The odd way of calling nx watch
here is because I've never managed to get it working any other way in a Windows/Powershell environment.
It's not a perfect setup, but for the most part it achieves what I set out to find. If anyone can think of ways to improve it, that'd be great!
Ah... now it's started misbehaving when I edit the API source code... at the first API call afterwards, it loses connection with the library build files and gives this error:
Exception: Worker was unable to load function myEndpoint: 'Cannot find module '@mylib/api-library'
Thanks @JonSilver for the workaround. The reason it doesn't work for you in the way you described is because there is a code that being injected in the final build files to support libraries recognition from the paths property in tsconfig file.
You could work around the issue (until I work a proper solution) by importing in your code the _importPaths.ts
file before importing any other lib. This is basically what I automatically inject in the build process.
Let me know if it works for you.
Where's that file, @AlexPshul? I can't seem to find it in my workspace.
Sorry @JonSilver, my bad.
I meant the _registerPaths.ts
file. It should be in the root directory of your function app.
@AlexPshul I'm so sorry, I'm probably being really thick, but how do I need to import my library?
My _registerPaths.ts
looks like this:
import { register } from "tsconfig-paths";
import * as tsConfig from "../../tsconfig.base.json";
import { CompilerOptions } from "typescript";
const compilerOptions = tsConfig.compilerOptions as unknown as CompilerOptions; // This is to avoid any problems with the typing system
if (compilerOptions.paths) {
const newPaths: Record<string, string[]> = Object.entries(compilerOptions.paths).reduce(
(newPathsObj, [pathKey, oldPaths]: [string, string[]]) => {
newPathsObj[pathKey] = oldPaths.map(path => path.replace(/.ts$/, ".js"));
return newPathsObj;
},
{} as Record<string, string[]>
);
register({
baseUrl: "dist",
paths: newPaths
});
}
What do I need to insert and where?
Great! That's the file!
In your function app, for each function you create, just call import '../_registerPaths'
as the first line in the file.
(If you are using V4 functions, you might need to change the path to the one that will suit your correct relative path)
Thanks, @AlexPshul, that seems to work really well. I now have that most beautiful of things, a smooth dev experience across isomorphic libraries, function apps and frontends, where everything hot reloads whenever something changes. Sweet! 🥇
Shall I leave this open for tracking the eventual official solution?
Yes, I'll mark it as closed once it's implemented. Thanks!
@JonSilver have a look at V.1.0.14. The start executor is now watching for changes. Let me know what you think.
It works! Thank you @AlexPshul
However library resolution and HMR seem very fragile. I have one project where it's working fine, and another where it's not working at all. :(
Hmmm that should not be the case. Maybe it has something to do with configuration? If you can reproduce this issue in a small repo on the side, I can have a look at it.
Hmmm that should not be the case. Maybe it has something to do with configuration? If you can reproduce this issue in a small repo on the side, I can have a look at it.
New issue... I found a bug! 😁
Thanks for creating this nx generator - it's much needed.
I'm developing a library for use with Azure Function Apps. Is it possible to have a library under development watched, hot rebuilt, and the function app hot reloaded whenever it or its dependent library changes? I've tried many things, but nothing worked. It seems like this highly necessary functionality might be an impossibility.