@capillarytech/cap-ui-dev-tools 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -0
- package/package.json +27 -0
- package/src/LibraryWatcherPlugin.js +126 -0
- package/src/index.js +7 -0
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Webpack Library Watcher Plugin
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/cap-ui-dev-tools)
|
|
4
|
+
[](https://travis-ci.org/your-username/cap-ui-dev-tools)
|
|
5
|
+
|
|
6
|
+
A "zero-config" webpack plugin that **automatically aliases and hot-reloads** local libraries. Drastically reduces development iteration time when working with shared component libraries or utilities.
|
|
7
|
+
|
|
8
|
+
This plugin solves the common "it takes forever to see my changes" problem in a micro-frontend or shared library architecture. It provides a single place to manage local library development, keeping your main webpack configuration clean.
|
|
9
|
+
|
|
10
|
+
## The Problem
|
|
11
|
+
|
|
12
|
+
When developing an application that consumes a local library, you typically have to manage two separate configurations:
|
|
13
|
+
1. **Webpack Alias:** You need to add a `resolve.alias` entry in your webpack config to point the library's package name to your local source code.
|
|
14
|
+
2. **File Watcher:** You need a separate mechanism to watch for changes in that local source code to trigger a rebuild.
|
|
15
|
+
|
|
16
|
+
Managing this in the main webpack config can be messy and error-prone.
|
|
17
|
+
|
|
18
|
+
## The Solution: "Explicit Alias" Mode
|
|
19
|
+
|
|
20
|
+
This plugin provides a single, clean interface to handle both aliasing and watching. You explicitly tell the plugin which libraries to manage, and it handles the complex webpack configuration for you.
|
|
21
|
+
|
|
22
|
+
**Based on initial testing, this can reduce development iteration time by over 90% (e.g., from 3 minutes to under 10 seconds).**
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install --save-dev cap-ui-dev-tools
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
Add the plugin to your `webpack.config.js` file. It should only be active in `development` mode.
|
|
33
|
+
|
|
34
|
+
### Configuration
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
// webpack.config.js
|
|
38
|
+
const LibraryWatcherPlugin = require('cap-ui-dev-tools');
|
|
39
|
+
const path = require('path');
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
mode: 'development',
|
|
43
|
+
// ... your other webpack config
|
|
44
|
+
|
|
45
|
+
// NO need for a separate resolve.alias block for local development!
|
|
46
|
+
// The plugin handles it for you.
|
|
47
|
+
resolve: {
|
|
48
|
+
// ... any other aliases can stay here
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
plugins: [
|
|
52
|
+
// Ensure you have HotModuleReplacementPlugin enabled
|
|
53
|
+
new webpack.HotModuleReplacementPlugin(),
|
|
54
|
+
|
|
55
|
+
new LibraryWatcherPlugin({
|
|
56
|
+
// An array of libraries to alias and watch.
|
|
57
|
+
libs: [
|
|
58
|
+
{
|
|
59
|
+
// The package name of the library (from its package.json).
|
|
60
|
+
name: 'cap-creatives-ui',
|
|
61
|
+
|
|
62
|
+
// The absolute path to the library's source code.
|
|
63
|
+
path: path.resolve(__dirname, '../cap-creatives-ui/src')
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'another-local-lib',
|
|
67
|
+
path: path.resolve(__dirname, '../another-local-lib/src')
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
|
|
71
|
+
// Enable verbose logging to the console.
|
|
72
|
+
verbose: true,
|
|
73
|
+
|
|
74
|
+
// Optional: Options passed directly to chokidar.
|
|
75
|
+
// See https://github.com/paulmillr/chokidar#api for details.
|
|
76
|
+
watchOptions: {
|
|
77
|
+
// ...
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
]
|
|
81
|
+
};
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## How It Works
|
|
85
|
+
|
|
86
|
+
1. During initialization, the plugin reads your `libs` array.
|
|
87
|
+
2. It **programmatically modifies webpack's configuration**, adding a `resolve.alias` entry for each library. This means you don't have to.
|
|
88
|
+
3. It then uses `chokidar` to watch the provided `path` for each library.
|
|
89
|
+
4. It adds the watched paths to webpack's `contextDependencies` to make webpack aware of them.
|
|
90
|
+
5. When a file change is detected, the plugin tells webpack's own watcher that the file has been invalidated, triggering a standard recompilation and HMR update.
|
|
91
|
+
|
|
92
|
+
## Troubleshooting
|
|
93
|
+
|
|
94
|
+
### Changes are not detected.
|
|
95
|
+
- **Check Paths**: Make sure the `path` in the `libs` configuration is an **absolute path** pointing to the **source code** of your library. Use `path.resolve(__dirname, '..', 'your-lib')`.
|
|
96
|
+
- **Check Name**: Ensure the `name` property matches the package name your host application is trying to import (e.g., `'cap-creatives-ui'`).
|
|
97
|
+
- **Enable `verbose` mode**: Set `verbose: true` to see detailed logs. It will confirm which aliases are being created and which paths are being watched.
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
ISC
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@capillarytech/cap-ui-dev-tools",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"main": "src/LibraryWatcherPlugin.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"start": "webpack serve",
|
|
7
|
+
"test": "jest"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [],
|
|
10
|
+
"author": "",
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"description": "",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"chokidar": "^3.6.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"eslint": "^8.57.1",
|
|
18
|
+
"jest": "^29.7.0",
|
|
19
|
+
"webpack": "^5.100.2",
|
|
20
|
+
"webpack-cli": "^5.1.4",
|
|
21
|
+
"webpack-dev-server": "^4.15.2",
|
|
22
|
+
"webpack-hot-middleware": "^2.26.1"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"webpack": "^5.0.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const chokidar = require('chokidar');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
class LibraryWatcherPlugin {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.options = {
|
|
8
|
+
libs: [],
|
|
9
|
+
watchOptions: {
|
|
10
|
+
ignored: /(^|[\/\\])\../,
|
|
11
|
+
persistent: true,
|
|
12
|
+
ignoreInitial: true,
|
|
13
|
+
awaitWriteFinish: {
|
|
14
|
+
stabilityThreshold: 200,
|
|
15
|
+
pollInterval: 100
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
verbose: false,
|
|
19
|
+
...options
|
|
20
|
+
};
|
|
21
|
+
this.watcher = null;
|
|
22
|
+
this.entryFile = null; // To hold the path to the host app's entry file
|
|
23
|
+
this.watchPaths = this.options.libs.map(lib => lib.path);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
apply(compiler) {
|
|
27
|
+
if (compiler.options.mode !== 'development' || !this.options.libs.length) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Step 1: Find the entry file to "touch" later
|
|
32
|
+
const { entry } = compiler.options;
|
|
33
|
+
let entryFile = null;
|
|
34
|
+
if (typeof entry === 'string') {
|
|
35
|
+
entryFile = entry;
|
|
36
|
+
} else if (Array.isArray(entry)) {
|
|
37
|
+
entryFile = entry[0];
|
|
38
|
+
} else if (typeof entry === 'object') {
|
|
39
|
+
// Find the first entry point in an object configuration
|
|
40
|
+
const firstKey = Object.keys(entry)[0];
|
|
41
|
+
const entryValue = entry[firstKey];
|
|
42
|
+
|
|
43
|
+
if (typeof entryValue === 'string') {
|
|
44
|
+
entryFile = entryValue;
|
|
45
|
+
} else if (Array.isArray(entryValue)) {
|
|
46
|
+
entryFile = entryValue[0];
|
|
47
|
+
} else if (typeof entryValue === 'object' && entryValue.import) {
|
|
48
|
+
// Handle { import: './path' } or { import: ['./path1'] }
|
|
49
|
+
entryFile = Array.isArray(entryValue.import) ? entryValue.import[0] : entryValue.import;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Resolve the entry file path to be absolute
|
|
54
|
+
if (entryFile) {
|
|
55
|
+
this.entryFile = path.resolve(compiler.context, entryFile);
|
|
56
|
+
} else {
|
|
57
|
+
console.warn('LibraryWatcherPlugin: Could not determine entry file to "touch".');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Step 2: Programmatically add aliases
|
|
61
|
+
const resolve = compiler.options.resolve || {};
|
|
62
|
+
const alias = resolve.alias || {};
|
|
63
|
+
|
|
64
|
+
this.options.libs.forEach(lib => {
|
|
65
|
+
if (this.options.verbose) {
|
|
66
|
+
console.log(`LibraryWatcherPlugin: Aliasing '${lib.name}' to '${lib.path}'`);
|
|
67
|
+
}
|
|
68
|
+
alias[lib.name] = lib.path;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
compiler.options.resolve = { ...resolve, alias };
|
|
72
|
+
|
|
73
|
+
// Step 3: Start the watcher
|
|
74
|
+
compiler.hooks.watchRun.tap('LibraryWatcherPlugin', () => {
|
|
75
|
+
this.startWatcher();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
compiler.hooks.watchClose.tap('LibraryWatcherPlugin', () => {
|
|
79
|
+
this.stopWatcher();
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
startWatcher() {
|
|
84
|
+
if (this.watcher || !this.watchPaths.length) return;
|
|
85
|
+
|
|
86
|
+
this.watcher = chokidar.watch(this.watchPaths, this.options.watchOptions);
|
|
87
|
+
|
|
88
|
+
const onFileChange = () => {
|
|
89
|
+
if (this.options.verbose) {
|
|
90
|
+
console.log(`LibraryWatcherPlugin: File change detected.`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (this.entryFile) {
|
|
94
|
+
// "Touch" the entry file to trigger webpack's own watcher
|
|
95
|
+
const now = new Date();
|
|
96
|
+
try {
|
|
97
|
+
fs.utimesSync(this.entryFile, now, now);
|
|
98
|
+
if (this.options.verbose) {
|
|
99
|
+
console.log(`LibraryWatcherPlugin: "Touched" ${this.entryFile} to trigger rebuild.`);
|
|
100
|
+
}
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.error(`LibraryWatcherPlugin: Error touching file: ${err}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
this.watcher
|
|
108
|
+
.on('add', onFileChange)
|
|
109
|
+
.on('change', onFileChange)
|
|
110
|
+
.on('unlink', onFileChange)
|
|
111
|
+
.on('error', (error) => console.error(`LibraryWatcherPlugin Error: ${error}`));
|
|
112
|
+
|
|
113
|
+
if (this.options.verbose) {
|
|
114
|
+
console.log(`LibraryWatcherPlugin: Watching for changes in ${this.watchPaths.join(', ')}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
stopWatcher() {
|
|
119
|
+
if (this.watcher) {
|
|
120
|
+
this.watcher.close();
|
|
121
|
+
this.watcher = null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = LibraryWatcherPlugin;
|