uivisor 0.1.0
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/LICENSE +21 -0
- package/README.md +173 -0
- package/dist/babel/index.d.ts +21 -0
- package/dist/babel/index.js +6 -0
- package/dist/chunk-4XKGGP26.js +32 -0
- package/dist/next/index.cjs +81 -0
- package/dist/next/loader.cjs +96 -0
- package/dist/overlay/index.d.ts +3 -0
- package/dist/overlay/index.js +1915 -0
- package/dist/vite/index.d.ts +13 -0
- package/dist/vite/index.js +80 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kazbek Kabdulov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="./uivisor.jpg" alt="uivisor" width="100%" />
|
|
4
|
+
|
|
5
|
+
<h3>Point at any element in your running app, tweak it by hand, and copy a precise,<br/>breakpoint-aware prompt for your AI agent — without ever touching your code.</h3>
|
|
6
|
+
|
|
7
|
+
<p>
|
|
8
|
+
<img alt="dev only" src="https://img.shields.io/badge/dev--only-never%20ships%20to%20prod-22c55e?style=flat-square" />
|
|
9
|
+
<img alt="stack" src="https://img.shields.io/badge/React%20·%20Next.js%20·%20Vite-4f46e5?style=flat-square" />
|
|
10
|
+
<img alt="prompt only" src="https://img.shields.io/badge/prompt--only-no%20code%20mutation-06b6d4?style=flat-square" />
|
|
11
|
+
<img alt="license" src="https://img.shields.io/badge/license-MIT-3f3f46?style=flat-square" />
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## The problem
|
|
19
|
+
|
|
20
|
+
You spot a small UI nit in your running app — this padding's too tight, that heading wants a heavier
|
|
21
|
+
weight, those cards need more radius at `lg`. Two bad options:
|
|
22
|
+
|
|
23
|
+
1. **Dig into the code** yourself for a one-line change, or
|
|
24
|
+
2. **Burn agent tokens** describing it in prose — *"on the pricing page, the middle card's button…"* — and hope the agent finds the right element.
|
|
25
|
+
|
|
26
|
+
**uivisor** is a third option. Turn it on, **click the element, nudge it with your mouse**, and it hands you a
|
|
27
|
+
copy-paste instruction that pins the **exact file & line**, the **styling mechanism**, the **breakpoint**, and
|
|
28
|
+
**what to change** — so your agent makes the edit in one shot. uivisor itself **never writes to your source**;
|
|
29
|
+
your tweaks are throwaway inline overrides in the browser.
|
|
30
|
+
|
|
31
|
+
## What you get
|
|
32
|
+
|
|
33
|
+
Click a button, bump its padding and color, hit **Copy prompt for agent**, and you get:
|
|
34
|
+
|
|
35
|
+
```md
|
|
36
|
+
# uivisor — apply these UI tweaks (2 changes across 1 element)
|
|
37
|
+
|
|
38
|
+
## 1. <button> "Get started" — src/components/PricingCard.tsx:26:7
|
|
39
|
+
- Identify by: component <PricingCard>, data-testid="buy-pro", selector `button[data-testid="buy-pro"]`
|
|
40
|
+
- Styling: tailwind (current classes: `mt-6 w-full rounded-md px-4 py-2 bg-indigo-600 text-white`)
|
|
41
|
+
- At lg breakpoint (≥1024px):
|
|
42
|
+
- padding: 16px → 24px → `lg:p-6`
|
|
43
|
+
- background-color: rgb(79,70,229) → #16a34a → `lg:bg-[#16a34a]`
|
|
44
|
+
|
|
45
|
+
### Rules
|
|
46
|
+
- Edit the EXISTING className/styles. Do NOT add inline styles or duplicate the component.
|
|
47
|
+
- Scope each change to its breakpoint with a responsive variant (e.g. `lg:`).
|
|
48
|
+
- Confirm the element by its source location, text and data-testid before editing.
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Paste that into Claude Code / Cursor / whatever — done. No more *"page 55, that thing"*.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Quick start
|
|
56
|
+
|
|
57
|
+
> uivisor runs **only in dev** (`apply: 'serve'` / gated to `next dev`). It is **physically absent from your
|
|
58
|
+
> production build.**
|
|
59
|
+
|
|
60
|
+
It isn't on npm yet, so for a real project link it locally (rebuild + restart picks up changes — no reinstall):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# in the uivisor folder
|
|
64
|
+
npm install && npm run build
|
|
65
|
+
|
|
66
|
+
# in YOUR project
|
|
67
|
+
npm i -D file:/absolute/path/to/uivisor
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Vite + React
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
// vite.config.ts
|
|
74
|
+
import { defineConfig } from 'vite'
|
|
75
|
+
import uivisor from 'uivisor/vite'
|
|
76
|
+
import react from '@vitejs/plugin-react'
|
|
77
|
+
|
|
78
|
+
export default defineConfig({
|
|
79
|
+
plugins: [uivisor(), react()], // ⚠️ uivisor() BEFORE react()
|
|
80
|
+
})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Next.js
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
// next.config.js
|
|
87
|
+
const { withUivisor } = require('uivisor/next')
|
|
88
|
+
|
|
89
|
+
/** @type {import('next').NextConfig} */
|
|
90
|
+
const nextConfig = { reactStrictMode: true }
|
|
91
|
+
|
|
92
|
+
module.exports = withUivisor(nextConfig)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
> Use plain `next dev` (webpack). Turbopack (`--turbo`) ignores `webpack()` config, so source locations
|
|
96
|
+
> won't be injected under it yet.
|
|
97
|
+
|
|
98
|
+
Then `npm run dev` and press **`Alt`+`U`** (or click the **◎** button bottom-right).
|
|
99
|
+
|
|
100
|
+
#### Keeping it updated while we iterate
|
|
101
|
+
|
|
102
|
+
Linked with `file:` → after any change to uivisor, just `npm run build` in the uivisor folder and **restart your
|
|
103
|
+
dev server**. No reinstall. (For Next, clear `.next/` if HMR doesn't pick it up.)
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Using the panel
|
|
108
|
+
|
|
109
|
+
1. **`Alt`+`U`** toggles uivisor · **Esc** deselects.
|
|
110
|
+
2. **Click any element.** A Figma-like inspector fills with its spacing / border / typography / fill —
|
|
111
|
+
only the controls that are relevant (Typography shows on text elements, Gap on flex/grid containers).
|
|
112
|
+
3. **Tweak:**
|
|
113
|
+
- **Combined-by-default** — Padding / Margin / Radius show one "all sides" input; click **▦** to split into
|
|
114
|
+
individual sides/corners.
|
|
115
|
+
- **Drag-to-scrub** — drag the icon on the left of any number field (cursor → ↔) to change it live.
|
|
116
|
+
- **Units** — line-height & letter-spacing have a px / % / em / × selector and always show the current number.
|
|
117
|
+
4. **Screen / breakpoint** — click `md` / `lg` / … and the app loads in a **virtual screen at that width**
|
|
118
|
+
(real CSS media queries reflow); drag the frame edge to fine-tune. Only your project's **real breakpoints**
|
|
119
|
+
are shown. Edits are scoped to the chosen breakpoint (`lg:p-6`). `Live` = your real window.
|
|
120
|
+
5. **Apply changes to** — pick the edit target:
|
|
121
|
+
- **All N like this** — when the element is a repeated sibling (same component/source, e.g. 3 cards), the
|
|
122
|
+
change previews on *all* of them and the prompt tells the agent to edit the shared component/source.
|
|
123
|
+
- **Only this one** · an existing **`.class`** · or **a new class you name** (agent creates it, leaves the rest).
|
|
124
|
+
6. **Copy prompt for agent** (or **Copy JSON** for the machine-readable spec).
|
|
125
|
+
|
|
126
|
+
Nothing is written to disk — tweaks live in the browser and vanish on reload.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## For your AI agent
|
|
131
|
+
|
|
132
|
+
uivisor's output is a **self-contained instruction**, designed to be acted on without extra context. When you
|
|
133
|
+
receive a uivisor prompt:
|
|
134
|
+
|
|
135
|
+
- **Go to the `file:line:col`** it names — that's the authoritative anchor (injected in dev, React-19 safe).
|
|
136
|
+
- **Edit the existing styling mechanism** it identifies (`tailwind` / `css-modules` / `styled-components` /
|
|
137
|
+
`inline` / `plain-css`) — not inline styles, and don't duplicate the component.
|
|
138
|
+
- **Respect the breakpoint scope** — emit the responsive variant (`lg:…`), don't make it global.
|
|
139
|
+
- **Respect the target** — `All N like this` means edit the shared component/class so every instance updates;
|
|
140
|
+
`new class` means create it and leave existing classes untouched; a positional `nth-of-type` selector is a
|
|
141
|
+
last-resort locator, never the thing to hard-code against.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## How it works
|
|
146
|
+
|
|
147
|
+
- **Source mapping** — a tiny dev-only Babel pass tags host JSX with `data-uiv-src="file:line:col"`
|
|
148
|
+
(Vite plugin runs it `enforce: 'pre'`; Next runs it as a webpack pre-loader, keeping SWC).
|
|
149
|
+
- **Identity, layered** — file:line → component name (from the file) → `data-testid` / id / stable selector / text.
|
|
150
|
+
- **Mechanism + tokens** — detects how the element is styled and reverse-maps px to Tailwind tokens
|
|
151
|
+
(`24px → p-6`, `leading-normal`, `tracking-tight`), with arbitrary-value fallback.
|
|
152
|
+
- **Breakpoints** — detected from the `@media` rules in your CSS, so the bar shows *your* breakpoints.
|
|
153
|
+
- **Responsive preview** — the app is loaded in a resizable iframe so real media queries reflow; the inspector
|
|
154
|
+
operates inside it.
|
|
155
|
+
|
|
156
|
+
## Limitations
|
|
157
|
+
|
|
158
|
+
- **Dev builds only** — production strips source info and mangles classes.
|
|
159
|
+
- **React-first** — the DOM/CSS/breakpoint core is framework-agnostic; only source mapping is React-specific today.
|
|
160
|
+
- **Tailwind-tuned tokens** — non-Tailwind stacks get raw px + selector guidance (the prompt says which
|
|
161
|
+
CSS-module / styled rule to edit).
|
|
162
|
+
|
|
163
|
+
## Develop
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
npm install
|
|
167
|
+
npm run build # tsup → dist/{vite,babel,overlay,next}
|
|
168
|
+
npm test # vitest (pure logic + babel transform)
|
|
169
|
+
npm run demo # Vite + React playground on :5180
|
|
170
|
+
# demo-next/ # Next.js (app router) playground on :5181
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
<div align="center"><sub>MIT · dev-only · prompt-only</sub></div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { types, PluginObj } from '@babel/core';
|
|
2
|
+
|
|
3
|
+
interface BabelAPI {
|
|
4
|
+
types: typeof types;
|
|
5
|
+
}
|
|
6
|
+
interface PluginState {
|
|
7
|
+
filename?: string;
|
|
8
|
+
cwd?: string;
|
|
9
|
+
opts: {
|
|
10
|
+
attr?: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Babel plugin: tag every intrinsic (host) JSX element with
|
|
15
|
+
* `data-uiv-src="relative/path.tsx:line:col"` so the overlay can resolve a
|
|
16
|
+
* clicked DOM node back to its exact source location. Dev-only; React-19 safe
|
|
17
|
+
* (does not rely on the removed fiber `_debugSource`).
|
|
18
|
+
*/
|
|
19
|
+
declare function uivisorBabel({ types: t }: BabelAPI): PluginObj<PluginState>;
|
|
20
|
+
|
|
21
|
+
export { uivisorBabel as default };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// src/babel/index.ts
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
function uivisorBabel({ types: t }) {
|
|
4
|
+
return {
|
|
5
|
+
name: "uivisor-source",
|
|
6
|
+
visitor: {
|
|
7
|
+
JSXOpeningElement(nodePath, state) {
|
|
8
|
+
const node = nodePath.node;
|
|
9
|
+
const name = node.name;
|
|
10
|
+
if (!t.isJSXIdentifier(name)) return;
|
|
11
|
+
if (!/^[a-z]/.test(name.name)) return;
|
|
12
|
+
if (!node.loc) return;
|
|
13
|
+
const attrName = state.opts.attr || "data-uiv-src";
|
|
14
|
+
const already = node.attributes.some(
|
|
15
|
+
(a) => t.isJSXAttribute(a) && t.isJSXIdentifier(a.name) && a.name.name === attrName
|
|
16
|
+
);
|
|
17
|
+
if (already) return;
|
|
18
|
+
const filename = state.filename || "unknown";
|
|
19
|
+
const cwd = state.cwd || process.cwd();
|
|
20
|
+
const rel = filename === "unknown" ? filename : path.relative(cwd, filename);
|
|
21
|
+
const value = `${rel}:${node.loc.start.line}:${node.loc.start.column + 1}`;
|
|
22
|
+
node.attributes.push(
|
|
23
|
+
t.jsxAttribute(t.jsxIdentifier(attrName), t.stringLiteral(value))
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
uivisorBabel
|
|
32
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/next/index.ts
|
|
31
|
+
var next_exports = {};
|
|
32
|
+
__export(next_exports, {
|
|
33
|
+
default: () => next_default,
|
|
34
|
+
withUivisor: () => withUivisor
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(next_exports);
|
|
37
|
+
var path = __toESM(require("path"), 1);
|
|
38
|
+
function withUivisor(nextConfig = {}, options = {}) {
|
|
39
|
+
const attr = options.attr || "data-uiv-src";
|
|
40
|
+
const loaderPath = path.join(__dirname, "loader.cjs");
|
|
41
|
+
const overlayPath = path.join(__dirname, "..", "overlay", "index.js");
|
|
42
|
+
return {
|
|
43
|
+
...nextConfig,
|
|
44
|
+
webpack(config, ctx) {
|
|
45
|
+
if (typeof nextConfig.webpack === "function") {
|
|
46
|
+
config = nextConfig.webpack(config, ctx);
|
|
47
|
+
}
|
|
48
|
+
if (!ctx.dev) return config;
|
|
49
|
+
config.module = config.module || {};
|
|
50
|
+
config.module.rules = config.module.rules || [];
|
|
51
|
+
config.module.rules.push({
|
|
52
|
+
test: /\.(jsx|tsx)$/,
|
|
53
|
+
exclude: /node_modules/,
|
|
54
|
+
enforce: "pre",
|
|
55
|
+
use: [{ loader: loaderPath, options: { attr } }]
|
|
56
|
+
});
|
|
57
|
+
if (!ctx.isServer) {
|
|
58
|
+
const prevEntry = config.entry;
|
|
59
|
+
config.entry = async () => {
|
|
60
|
+
const entries = typeof prevEntry === "function" ? await prevEntry() : prevEntry;
|
|
61
|
+
for (const key of ["main-app", "main.js", "main"]) {
|
|
62
|
+
const e = entries[key];
|
|
63
|
+
if (!e) continue;
|
|
64
|
+
if (Array.isArray(e)) {
|
|
65
|
+
if (!e.includes(overlayPath)) e.unshift(overlayPath);
|
|
66
|
+
} else if (e && Array.isArray(e.import)) {
|
|
67
|
+
if (!e.import.includes(overlayPath)) e.import.unshift(overlayPath);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return entries;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return config;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
var next_default = withUivisor;
|
|
78
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
79
|
+
0 && (module.exports = {
|
|
80
|
+
withUivisor
|
|
81
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/next/loader.ts
|
|
31
|
+
var loader_exports = {};
|
|
32
|
+
__export(loader_exports, {
|
|
33
|
+
default: () => uivisorNextLoader
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(loader_exports);
|
|
36
|
+
var import_core = require("@babel/core");
|
|
37
|
+
|
|
38
|
+
// src/babel/index.ts
|
|
39
|
+
var path = __toESM(require("path"), 1);
|
|
40
|
+
function uivisorBabel({ types: t }) {
|
|
41
|
+
return {
|
|
42
|
+
name: "uivisor-source",
|
|
43
|
+
visitor: {
|
|
44
|
+
JSXOpeningElement(nodePath, state) {
|
|
45
|
+
const node = nodePath.node;
|
|
46
|
+
const name = node.name;
|
|
47
|
+
if (!t.isJSXIdentifier(name)) return;
|
|
48
|
+
if (!/^[a-z]/.test(name.name)) return;
|
|
49
|
+
if (!node.loc) return;
|
|
50
|
+
const attrName = state.opts.attr || "data-uiv-src";
|
|
51
|
+
const already = node.attributes.some(
|
|
52
|
+
(a) => t.isJSXAttribute(a) && t.isJSXIdentifier(a.name) && a.name.name === attrName
|
|
53
|
+
);
|
|
54
|
+
if (already) return;
|
|
55
|
+
const filename = state.filename || "unknown";
|
|
56
|
+
const cwd = state.cwd || process.cwd();
|
|
57
|
+
const rel = filename === "unknown" ? filename : path.relative(cwd, filename);
|
|
58
|
+
const value = `${rel}:${node.loc.start.line}:${node.loc.start.column + 1}`;
|
|
59
|
+
node.attributes.push(
|
|
60
|
+
t.jsxAttribute(t.jsxIdentifier(attrName), t.stringLiteral(value))
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/next/loader.ts
|
|
68
|
+
function uivisorNextLoader(source, inMap) {
|
|
69
|
+
const callback = this.async();
|
|
70
|
+
const resourcePath = this.resourcePath || "";
|
|
71
|
+
if (!/\.[jt]sx$/.test(resourcePath) || !source.includes("<")) {
|
|
72
|
+
callback(null, source, inMap);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const attr = this.getOptions?.().attr || "data-uiv-src";
|
|
76
|
+
try {
|
|
77
|
+
const result = (0, import_core.transformSync)(source, {
|
|
78
|
+
filename: resourcePath,
|
|
79
|
+
cwd: this.rootContext || process.cwd(),
|
|
80
|
+
babelrc: false,
|
|
81
|
+
configFile: false,
|
|
82
|
+
sourceMaps: true,
|
|
83
|
+
parserOpts: { plugins: ["jsx", "typescript"], sourceType: "module" },
|
|
84
|
+
generatorOpts: { retainLines: true },
|
|
85
|
+
plugins: [[uivisorBabel, { attr }]]
|
|
86
|
+
});
|
|
87
|
+
if (!result?.code) {
|
|
88
|
+
callback(null, source, inMap);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
callback(null, result.code, result.map ?? inMap);
|
|
92
|
+
} catch {
|
|
93
|
+
callback(null, source, inMap);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (module.exports && module.exports.default) module.exports = module.exports.default;
|