typescriptdependenciesvitenpm-workspaces

How do I use Vite to rebuild dependencies with npm workspaces?


Similar to this question, referencing Yarn workspaces, I have the following npm workspace structure:

package.json // root
packages
    @myscope/a
        package.json
        tsconfig.json
    @myscope/b
        package.json
        tsconfig.json
    @myscope/c
        package.json
        tsconfig.json

These packages are referenced in the root package JSON as follows

{
    "workspaces": [
        "packages/*",
    ],
}

@myscope/c depends on @myscope/b, and @myscope/b depends on @myscope/a.

Each package has its own build command and its own tsconfig.json:

{
    "build": "tsc --build --verbose tsconfig.json",
}

The tsc command builds types as well as the JS, which are critical for the imports to work when developing with local packages. I could switch these commands for a vite.config.ts that builds the JS->TS but types are not emitted. I know I can use vite-plugin-dts for this purpose.

Vite Command

If I'm running @myscope/c with the following command from within the @myscope/c folder:

npx vite serve --mode=development --config vite.config.ts

Example Repo

I've created an example repository demonstrating the layout. Inside it, running @myscope/c with vite will not rebuild @myscope/b or @myscope/a when they change despite having included preserveSymlinks in my vite.config.ts.

...
    resolve: {
        preserveSymlinks: true // this is the fix from yargs question
    }
})

Questions


Solution

  • The answer for yarn workspaces, preserveSymlinks, doesn't work for npm workspaces.

    I had to write a Vite plugin to watch my local npm workspace packages and then handle updates to those files.

    The gist of the plugin is:

    
    export const VitePluginWatchWorkspace = async (config: VitePluginWatchExternalOptions): Promise<Plugin<any>> => {
    
        // get a list of external files you want to watch
        const externalFiles = await getExternalFileLists()
    
    
        return {
            name: 'vite-plugin-watch-workspace',
    
            // on build start, add the external files to Vite's watch list
            async buildStart() {
                Object.keys(externalFiles).map((file) => {
                    this.addWatchFile(file)
                })
            },
    
            // when the external files change, rebuild them with esbuild
            async handleHotUpdate({ file, server }) {
                log(`File', ${file}`)
    
                const tsconfigPath = externalFiles[file]
                if (!tsconfigPath) {
                    log(`tsconfigPath not found for file ${file}`)
                    return
                }
                const tsconfig = getTsConfigFollowExtends(tsconfigPath)
                const fileExtension = path.extname(file)
                const loader = getLoader(fileExtension)
                const outdir = getOutDir(file, tsconfig)
                const outfile = getOutFile(outdir, file, fileExtension)
                log(`Outfile ${outfile}, loader ${loader}`)
                const buildResult = await build({
                    tsconfig: tsconfigPath,
                    stdin: {
                        contents: fs.readFileSync(file, 'utf8'),
                        loader,
                        resolveDir: path.dirname(file),
                    },
                    outfile,
                    platform: config.format === 'cjs' ? 'node' : 'neutral',
                    format: config.format || 'esm',
                })
                log(`buildResult', ${JSON.stringify(buildResult)}`)
    
    
                // tell the server that the file has updated
                server.ws.send({
                    type: 'update',
                    updates: [
                        {
                            acceptedPath: file,
                            type: 'js-update',
                            path: file,
                            timestamp: Date.now(),
                        },
                    ],
                })
            },
        }
    }
    

    You can download the plugin here and view the source code here.