├── src ├── index.ts └── lib │ └── plugin.ts ├── .gitattributes ├── .gitignore ├── tsconfig.json ├── .github └── workflows │ └── npm-publish-github-packages.yml ├── package.json └── README.md /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/plugin'; 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | tmp 4 | npm-debug.log* 5 | .package.json 6 | 7 | # Lock files shouldn't be committed for libraries https://stackoverflow.com/a/40206145 8 | yarn.lock 9 | package-lock.json -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": true, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "commonjs", 13 | "lib": ["es2017", "dom"], 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "@nnn/plugin": ["src/index.ts"] 19 | }, 20 | "outDir": "./dist", 21 | "types": ["node"] 22 | }, 23 | "include": ["**/*.ts"], 24 | "exclude": ["node_modules", "tmp"] 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish-github-packages.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: actions/setup-node@v3 17 | with: 18 | node-version: 16 19 | - run: npm ci 20 | - run: npm test 21 | 22 | publish-gpr: 23 | needs: build 24 | runs-on: ubuntu-latest 25 | permissions: 26 | contents: read 27 | packages: write 28 | steps: 29 | - uses: actions/checkout@v3 30 | - uses: actions/setup-node@v3 31 | with: 32 | node-version: 16 33 | registry-url: https://registry.npmjs.org 34 | - run: npm i 35 | - run: npm build 36 | - run: npm publish 37 | env: 38 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-nx-build-coordination-plugin", 3 | "version": "0.0.8", 4 | "description": "Use to coordinate the compiling of the libs and the webpack linking.", 5 | "main": "dist/src/index.js", 6 | "engines": { 7 | "node": ">=10.x.x" 8 | }, 9 | "scripts": { 10 | "build": "rm -rf dist && tsc -b", 11 | "prepublish": "npm run build" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/wscats/webpack-nx-build-coordination-plugin.git" 16 | }, 17 | "keywords": [ 18 | "webpack", 19 | "plugin", 20 | "run-command", 21 | "build", 22 | "nx", 23 | "nrwl", 24 | "monorepo" 25 | ], 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/wscats/webpack-nx-build-coordination-plugin/issues" 29 | }, 30 | "homepage": "https://github.com/wscats/webpack-nx-build-coordination-plugin", 31 | "files": [ 32 | "index.js", 33 | "dist/" 34 | ], 35 | "dependencies": { 36 | "chokidar": "^3.5.3", 37 | "tslib": "^2.0.0" 38 | }, 39 | "devDependencies": { 40 | "@types/node": "14.14.33", 41 | "typescript": "~4.1.4" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Incremental Builds 2 | 3 | After you cloned the repo, run: 4 | 5 | ## Running without tsc -b -w 6 | 7 | 1. `nx serve nextapp` 8 | 2. Open `header.ts`, change something, and see it being reflected. 9 | 10 | By default, the serve command is going to run `tsc -b` without -w. I did it this way cause it was the easiest way to show how it all works. 11 | 12 | Note, that the `dist` folder should contain `tsconfig.tsbuildinfo` files. They should be cached, to speed up ts execution, so local dev will benefit from the CI stuff. 13 | 14 | The repo hardcodes references between projects. Instead, a custom Nx executor should use the provided project graph to generate those. It's easy to do. We do similar things for other tools and even for tsconfig path mappings. 15 | 16 | ## Running with tsc -b -w 17 | 18 | Open next.config.js and comment out the following line: 19 | 20 | ```js 21 | plugins: [...config.plugins, new WebpackNxBuildCoordinationPlugin("tsc -b apps/nextapp/tsconfig.json", "libs")], 22 | # OR 23 | # options see: https://www.npmjs.com/package/chokidar 24 | plugins: [...config.plugins, new WebpackNxBuildCoordinationPlugin("tsc -b apps/nextapp/tsconfig.json", "libs", [options])], 25 | ``` 26 | 27 | Run `nx buildlibs nextapp` in one terminal and `nx serve nextapp` in another one. `nx buildlibs nextapp` will run tsc with watch, so it's super fast. In a real setup, WebpackNxBuildCoordinationPlugin should run the watch command, look at output, and do the synchronization, same was WebpackNxBuildCoordinationPlugin does it right now. It's not super hard, but it requires a bit more work. 28 | 29 | ## Handling Non-TS Files 30 | 31 | If you handle non-ts files, you can have a watch that invokes a target on a lib, and it will copy the files where they belong. You can also inline them into the JS output if you write a TS transformer. 32 | -------------------------------------------------------------------------------- /src/lib/plugin.ts: -------------------------------------------------------------------------------- 1 | import { watch, WatchOptions } from 'chokidar'; 2 | import { execSync } from 'child_process'; 3 | 4 | export class WebpackNxBuildCoordinationPlugin { 5 | private currentlyRunning: 'none' | 'nx-build' | 'webpack-build' = 'none'; 6 | 7 | constructor( 8 | private readonly buildCmd: string | (() => string), 9 | private readonly projectsFolderToWatch: string, 10 | private readonly options?: WatchOptions 11 | ) { 12 | this.buildChangedProjects(); 13 | this.startWatchingBuildableLibs(); 14 | } 15 | 16 | apply(compiler) { 17 | compiler.hooks.beforeCompile.tapPromise( 18 | 'IncrementalDevServerPlugin', 19 | async () => { 20 | while (this.currentlyRunning === 'nx-build') { 21 | await sleep(50); 22 | } 23 | this.currentlyRunning = 'webpack-build'; 24 | } 25 | ); 26 | compiler.hooks.done.tapPromise('IncrementalDevServerPlugin', async () => { 27 | this.currentlyRunning = 'none'; 28 | }); 29 | } 30 | 31 | startWatchingBuildableLibs() { 32 | const watcher = watch(this.projectsFolderToWatch, { 33 | cwd: process.cwd(), 34 | ignoreInitial: true, 35 | ...this.options, 36 | }); 37 | 38 | watcher.on('all', (_event: string, path: string) => { 39 | this.buildChangedProjects(); 40 | }); 41 | } 42 | 43 | async buildChangedProjects() { 44 | while (this.currentlyRunning === 'webpack-build') { 45 | await sleep(50); 46 | } 47 | this.currentlyRunning = 'nx-build'; 48 | try { 49 | execSync(typeof this.buildCmd === 'function' ? 50 | this.buildCmd() : this.buildCmd, { stdio: [0, 1, 2] }); 51 | // eslint-disable-next-line no-empty 52 | } catch (e) { 53 | } 54 | this.currentlyRunning = 'none'; 55 | } 56 | } 57 | 58 | function sleep(time: number) { 59 | return new Promise((resolve) => setTimeout(resolve, time)); 60 | } 61 | --------------------------------------------------------------------------------