@lego-build/plugins 0.0.1 → 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/dist/index.d.ts CHANGED
@@ -3,10 +3,32 @@ declare function setupIframeBridge(): void;
3
3
 
4
4
  declare function setupElementSelector(): void;
5
5
 
6
+ interface SourceLocatorOptions {
7
+ /** Files to include */
8
+ include?: string | string[];
9
+ /** Files to exclude */
10
+ exclude?: string | string[];
11
+ /** Prefix for data attributes (default: 'locator') */
12
+ prefix?: string;
13
+ /** Enable/disable the plugin (default: true) */
14
+ enable?: boolean;
15
+ /** Custom filter for tags */
16
+ filterTag?: string[] | ((tag: string) => boolean);
17
+ }
18
+ interface TransformResult {
19
+ code: string;
20
+ map: any;
21
+ }
6
22
  /**
7
- * Auto-initialize iframe bridge when running in iframe.
8
- * Call this in your entry point or use setupIframeBridge/setupElementSelector manually.
23
+ * Vite plugin for adding source locator attributes to JSX elements.
24
+ * This enables click-to-source functionality in the preview iframe.
9
25
  */
10
- declare function initIframeBridge(): void;
26
+ declare function sourceLocator(options?: SourceLocatorOptions): {
27
+ name: string;
28
+ enforce: "pre";
29
+ apply: "serve";
30
+ version: string;
31
+ transform(code: string, id: string): Promise<TransformResult | null | undefined>;
32
+ };
11
33
 
12
- export { initIframeBridge, sendToParent, setupElementSelector, setupIframeBridge };
34
+ export { type SourceLocatorOptions, sendToParent, setupElementSelector, setupIframeBridge, sourceLocator };
package/dist/index.js CHANGED
@@ -548,19 +548,193 @@ function setupElementSelector() {
548
548
  });
549
549
  }
550
550
 
551
- // src/index.ts
552
- function initIframeBridge() {
553
- try {
554
- if (window.parent !== window) {
555
- setupIframeBridge();
556
- setupElementSelector();
551
+ // src/source-locator.ts
552
+ import path from "path";
553
+ import * as parser from "@babel/parser";
554
+ import traverse from "@babel/traverse";
555
+ import generate from "@babel/generator";
556
+ import * as t from "@babel/types";
557
+ import micromatch from "micromatch";
558
+ import { createHash } from "crypto";
559
+ var HTML_TAGS = [
560
+ "div",
561
+ "span",
562
+ "p",
563
+ "h1",
564
+ "h2",
565
+ "h3",
566
+ "h4",
567
+ "h5",
568
+ "h6",
569
+ "button",
570
+ "a",
571
+ "img",
572
+ "input",
573
+ "textarea",
574
+ "select",
575
+ "form",
576
+ "ul",
577
+ "ol",
578
+ "li",
579
+ "table",
580
+ "thead",
581
+ "tbody",
582
+ "tr",
583
+ "td",
584
+ "th",
585
+ "header",
586
+ "footer",
587
+ "nav",
588
+ "main",
589
+ "section",
590
+ "article",
591
+ "aside",
592
+ "label",
593
+ "strong",
594
+ "em",
595
+ "code",
596
+ "pre",
597
+ "blockquote"
598
+ ];
599
+ var TEXT_TAGS = ["label", "span", "button", "p", "div", "h1", "h2", "h3", "h4", "h5", "h6"];
600
+ function getWorkspaceRoot(filePath) {
601
+ let dir = path.resolve(filePath);
602
+ const { root } = path.parse(dir);
603
+ while (dir !== root) {
604
+ if (path.basename(dir) === "workspace") return dir;
605
+ dir = path.dirname(dir);
606
+ }
607
+ return path.resolve(process.cwd(), "..");
608
+ }
609
+ function shouldProcessTag(tagName, options) {
610
+ if (options.filterTag) {
611
+ if (Array.isArray(options.filterTag)) {
612
+ return options.filterTag.includes(tagName);
613
+ }
614
+ return options.filterTag(tagName);
615
+ }
616
+ return HTML_TAGS.includes(tagName.toLowerCase());
617
+ }
618
+ function getElementTextContent2(nodePath) {
619
+ const parent = nodePath.parent;
620
+ if (!t.isJSXElement(parent)) return "";
621
+ let text = "";
622
+ let hasExpression = false;
623
+ for (const child of parent.children) {
624
+ if (t.isJSXText(child)) {
625
+ text += child.value;
626
+ } else if (t.isJSXExpressionContainer(child)) {
627
+ hasExpression = true;
628
+ }
629
+ }
630
+ return hasExpression ? "" : text.trim();
631
+ }
632
+ function transformJSX(code, filePath, prefix, options) {
633
+ const ast = parser.parse(code, {
634
+ sourceType: "module",
635
+ plugins: ["jsx", "typescript", "classProperties", "decorators-legacy"]
636
+ });
637
+ const workspaceRoot = getWorkspaceRoot(filePath);
638
+ const relativePath = path.relative(workspaceRoot, filePath);
639
+ let hasChanges = false;
640
+ traverse(ast, {
641
+ JSXOpeningElement(nodePath) {
642
+ const node = nodePath.node;
643
+ const hasLocatorAttr = node.attributes.some(
644
+ (attr) => t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && attr.name.name.startsWith(`data-${prefix}`)
645
+ );
646
+ if (hasLocatorAttr) return;
647
+ if (t.isJSXIdentifier(node.name) && node.name.name === "Fragment") return;
648
+ if (t.isJSXMemberExpression(node.name) && t.isJSXIdentifier(node.name.object) && node.name.object.name === "React" && t.isJSXIdentifier(node.name.property) && node.name.property.name === "Fragment")
649
+ return;
650
+ let tagName = "";
651
+ if (t.isJSXIdentifier(node.name)) {
652
+ tagName = node.name.name;
653
+ }
654
+ if (!shouldProcessTag(tagName, options)) return;
655
+ const line = node.loc?.start.line;
656
+ const column = node.loc?.start.column;
657
+ if (!line || column === void 0) return;
658
+ const textContent = TEXT_TAGS.includes(tagName.toLowerCase()) ? getElementTextContent2(nodePath) : "";
659
+ const attributes = [
660
+ t.jsxAttribute(t.jsxIdentifier(`data-${prefix}-path`), t.stringLiteral(relativePath)),
661
+ t.jsxAttribute(t.jsxIdentifier(`data-${prefix}-line`), t.stringLiteral(String(line))),
662
+ t.jsxAttribute(t.jsxIdentifier(`data-${prefix}-column`), t.stringLiteral(String(column))),
663
+ t.jsxAttribute(t.jsxIdentifier(`data-${prefix}-tag`), t.stringLiteral(tagName))
664
+ ];
665
+ if (textContent) {
666
+ attributes.push(
667
+ t.jsxAttribute(
668
+ t.jsxIdentifier(`data-${prefix}-text`),
669
+ t.stringLiteral(textContent)
670
+ )
671
+ );
672
+ }
673
+ const spreadIndex = node.attributes.findIndex((attr) => t.isJSXSpreadAttribute(attr));
674
+ if (spreadIndex === -1) {
675
+ node.attributes.push(...attributes);
676
+ } else {
677
+ node.attributes.splice(spreadIndex, 0, ...attributes);
678
+ }
679
+ hasChanges = true;
680
+ }
681
+ });
682
+ if (!hasChanges) return null;
683
+ const result = generate(ast, {}, code);
684
+ return {
685
+ code: result.code,
686
+ map: result.map
687
+ };
688
+ }
689
+ var transformCache = /* @__PURE__ */ new Map();
690
+ function sourceLocator(options = {}) {
691
+ const {
692
+ include = ["src/**/*.{jsx,tsx,js,ts}"],
693
+ exclude = [],
694
+ prefix = "locator",
695
+ enable = true
696
+ } = options;
697
+ const includePatterns = Array.isArray(include) ? include : [include];
698
+ const excludePatterns = Array.isArray(exclude) ? exclude : [exclude];
699
+ return {
700
+ name: "@lego/plugin-source-locator",
701
+ enforce: "pre",
702
+ apply: "serve",
703
+ version: "0.0.1",
704
+ async transform(code, id) {
705
+ if (!enable) return;
706
+ const relativePath = path.relative(process.cwd(), id);
707
+ const isIncluded = micromatch.isMatch(relativePath, includePatterns);
708
+ const isExcluded = excludePatterns.length > 0 && micromatch.isMatch(relativePath, excludePatterns);
709
+ if (!isIncluded || isExcluded) return;
710
+ const ext = path.extname(id);
711
+ if (![".jsx", ".tsx", ".js", ".ts"].includes(ext)) return;
712
+ const hash = createHash("md5").update(code).digest("hex");
713
+ const cacheKey = `${id}:${hash}`;
714
+ if (transformCache.has(cacheKey)) {
715
+ return transformCache.get(cacheKey);
716
+ }
717
+ const result = transformJSX(code, id, prefix, options);
718
+ if (result) {
719
+ transformCache.set(cacheKey, result);
720
+ return result;
721
+ }
722
+ return null;
557
723
  }
558
- } catch {
724
+ };
725
+ }
726
+
727
+ // src/index.ts
728
+ try {
729
+ if (typeof window !== "undefined" && window.parent !== window) {
730
+ setupIframeBridge();
731
+ setupElementSelector();
559
732
  }
733
+ } catch {
560
734
  }
561
735
  export {
562
- initIframeBridge,
563
736
  sendToParent,
564
737
  setupElementSelector,
565
- setupIframeBridge
738
+ setupIframeBridge,
739
+ sourceLocator
566
740
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lego-build/plugins",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -8,20 +8,36 @@
8
8
  ".": {
9
9
  "types": "./dist/index.d.ts",
10
10
  "import": "./dist/index.js"
11
- },
12
- "./auto": {
13
- "import": "./dist/auto.js"
14
11
  }
15
12
  },
16
13
  "files": [
17
14
  "dist"
18
15
  ],
19
16
  "scripts": {
20
- "build": "tsup src/index.ts src/auto.ts --format esm --dts",
21
- "dev": "tsup src/index.ts src/auto.ts --format esm --dts --watch"
17
+ "build": "tsup src/index.ts --format esm --dts",
18
+ "dev": "tsup src/index.ts --format esm --dts --watch"
19
+ },
20
+ "dependencies": {
21
+ "@babel/generator": "^7.27.0",
22
+ "@babel/parser": "^7.27.0",
23
+ "@babel/traverse": "^7.27.0",
24
+ "@babel/types": "^7.27.0",
25
+ "micromatch": "^4.0.5"
22
26
  },
23
27
  "devDependencies": {
28
+ "@types/babel__generator": "^7.27.0",
29
+ "@types/babel__traverse": "^7.20.7",
30
+ "@types/micromatch": "^4.0.9",
31
+ "@types/node": "^22",
24
32
  "tsup": "^8.0.0",
25
33
  "typescript": "^5.0.0"
34
+ },
35
+ "peerDependencies": {
36
+ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0"
37
+ },
38
+ "peerDependenciesMeta": {
39
+ "vite": {
40
+ "optional": true
41
+ }
26
42
  }
27
43
  }