@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 ADDED
@@ -0,0 +1,101 @@
1
+ # Webpack Library Watcher Plugin
2
+
3
+ [![NPM version](https://img.shields.io/npm/v/cap-ui-dev-tools.svg)](https://www.npmjs.com/package/cap-ui-dev-tools)
4
+ [![Build Status](https://img.shields.io/travis/your-username/cap-ui-dev-tools.svg)](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;
package/src/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import '@shared-ui-lib/index.js';
2
+
3
+ console.log("Webpack entry point loaded.");
4
+
5
+ if (module.hot) {
6
+ module.hot.accept();
7
+ }