@frmwrkd/snag-anchor 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 +19 -0
- package/README.md +305 -0
- package/dist/babel/component-name.d.ts +42 -0
- package/dist/babel/component-name.d.ts.map +1 -0
- package/dist/babel/component-name.js +107 -0
- package/dist/babel/component-name.js.map +1 -0
- package/dist/babel/plugin.d.ts +69 -0
- package/dist/babel/plugin.d.ts.map +1 -0
- package/dist/babel/plugin.js +272 -0
- package/dist/babel/plugin.js.map +1 -0
- package/dist/babel/walk.d.ts +8 -0
- package/dist/babel/walk.d.ts.map +1 -0
- package/dist/babel/walk.js +50 -0
- package/dist/babel/walk.js.map +1 -0
- package/dist/devtools-json.d.ts +45 -0
- package/dist/devtools-json.d.ts.map +1 -0
- package/dist/devtools-json.js +117 -0
- package/dist/devtools-json.js.map +1 -0
- package/dist/esbuild.d.ts +3 -0
- package/dist/esbuild.d.ts.map +1 -0
- package/dist/esbuild.js +11 -0
- package/dist/esbuild.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +90 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.cjs +44 -0
- package/dist/loader.cjs.map +1 -0
- package/dist/loader.d.cts +2 -0
- package/dist/loader.d.cts.map +1 -0
- package/dist/magic-string.d.ts +23 -0
- package/dist/magic-string.d.ts.map +1 -0
- package/dist/magic-string.js +23 -0
- package/dist/magic-string.js.map +1 -0
- package/dist/nuxt.d.ts +14 -0
- package/dist/nuxt.d.ts.map +1 -0
- package/dist/nuxt.js +29 -0
- package/dist/nuxt.js.map +1 -0
- package/dist/options.d.ts +101 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +122 -0
- package/dist/options.js.map +1 -0
- package/dist/rollup.d.ts +3 -0
- package/dist/rollup.d.ts.map +1 -0
- package/dist/rollup.js +11 -0
- package/dist/rollup.js.map +1 -0
- package/dist/root.d.ts +38 -0
- package/dist/root.d.ts.map +1 -0
- package/dist/root.js +101 -0
- package/dist/root.js.map +1 -0
- package/dist/rspack.d.ts +3 -0
- package/dist/rspack.d.ts.map +1 -0
- package/dist/rspack.js +11 -0
- package/dist/rspack.js.map +1 -0
- package/dist/stamp.d.ts +66 -0
- package/dist/stamp.d.ts.map +1 -0
- package/dist/stamp.js +28 -0
- package/dist/stamp.js.map +1 -0
- package/dist/transform.d.ts +14 -0
- package/dist/transform.d.ts.map +1 -0
- package/dist/transform.js +54 -0
- package/dist/transform.js.map +1 -0
- package/dist/vite.d.ts +3 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +11 -0
- package/dist/vite.js.map +1 -0
- package/dist/vue/transform.d.ts +50 -0
- package/dist/vue/transform.d.ts.map +1 -0
- package/dist/vue/transform.js +188 -0
- package/dist/vue/transform.js.map +1 -0
- package/dist/webpack.d.ts +3 -0
- package/dist/webpack.d.ts.map +1 -0
- package/dist/webpack.js +15 -0
- package/dist/webpack.js.map +1 -0
- package/package.json +84 -0
package/dist/root.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repo-root detection and path normalisation.
|
|
3
|
+
*
|
|
4
|
+
* doc 02 §2.1: "Paths are **repo-relative and POSIX-normalised**. The absolute
|
|
5
|
+
* project root is resolved separately (§2.5), so the same attribute works
|
|
6
|
+
* whether the app runs locally, in a container, or on a preview deploy."
|
|
7
|
+
*
|
|
8
|
+
* Two invariants this module exists to hold:
|
|
9
|
+
*
|
|
10
|
+
* 1. **Every emitted path is repo-relative.** An absolute path baked into a
|
|
11
|
+
* `data-snag` attribute is machine-specific: it breaks in a container, it
|
|
12
|
+
* breaks for a teammate, and it leaks the developer's home directory into
|
|
13
|
+
* the DOM. The absolute root is served once, out of band, by
|
|
14
|
+
* `devtools-json.ts`.
|
|
15
|
+
* 2. **Every emitted path is POSIX-normalised.** `src\checkout\PromoField.tsx`
|
|
16
|
+
* and `src/checkout/PromoField.tsx` must be the same string, or a capture
|
|
17
|
+
* filed on Windows will not match a repo map built on Linux and
|
|
18
|
+
* `suspect_files` silently stops resolving.
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync } from 'node:fs';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
/**
|
|
23
|
+
* Backslashes → forward slashes. Applied to every path this package emits.
|
|
24
|
+
*
|
|
25
|
+
* Note this is deliberately *not* `path.posix.normalize` — we do not want to
|
|
26
|
+
* collapse `..` segments or otherwise rewrite a path the caller gave us, only
|
|
27
|
+
* to make the separator identical on every platform.
|
|
28
|
+
*/
|
|
29
|
+
export function toPosix(p) {
|
|
30
|
+
return p.replace(/\\/g, '/');
|
|
31
|
+
}
|
|
32
|
+
/** Cache keyed by the resolved start directory. Transform runs per file. */
|
|
33
|
+
const gitRootCache = new Map();
|
|
34
|
+
const manifestDirCache = new Map();
|
|
35
|
+
/** Test seam — the caches are process-global and would otherwise leak between tests. */
|
|
36
|
+
export function clearRootCaches() {
|
|
37
|
+
gitRootCache.clear();
|
|
38
|
+
manifestDirCache.clear();
|
|
39
|
+
}
|
|
40
|
+
function findUp(startDir, entry, cache) {
|
|
41
|
+
const start = path.resolve(startDir);
|
|
42
|
+
if (cache.has(start))
|
|
43
|
+
return cache.get(start);
|
|
44
|
+
let dir = start;
|
|
45
|
+
for (;;) {
|
|
46
|
+
if (existsSync(path.join(dir, entry))) {
|
|
47
|
+
cache.set(start, dir);
|
|
48
|
+
return dir;
|
|
49
|
+
}
|
|
50
|
+
const parent = path.dirname(dir);
|
|
51
|
+
if (parent === dir)
|
|
52
|
+
break;
|
|
53
|
+
dir = parent;
|
|
54
|
+
}
|
|
55
|
+
cache.set(start, undefined);
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Nearest ancestor directory containing `.git`.
|
|
60
|
+
*
|
|
61
|
+
* `.git` is tested with `existsSync`, not `isDirectory`, on purpose: in a git
|
|
62
|
+
* worktree or a submodule `.git` is a *file* containing a `gitdir:` pointer.
|
|
63
|
+
* Treating it as "not a repo" would push the root up to the parent checkout and
|
|
64
|
+
* every emitted path would gain a bogus prefix.
|
|
65
|
+
*/
|
|
66
|
+
export function findGitRoot(startDir) {
|
|
67
|
+
return findUp(startDir, '.git', gitRootCache);
|
|
68
|
+
}
|
|
69
|
+
/** Nearest ancestor directory containing `package.json`. Fallback when there is no git repo. */
|
|
70
|
+
export function findManifestDir(startDir) {
|
|
71
|
+
return findUp(startDir, 'package.json', manifestDirCache);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The project root every emitted path is relative to.
|
|
75
|
+
*
|
|
76
|
+
* Precedence: explicit `root` option → git root → nearest `package.json` →
|
|
77
|
+
* the working directory. Always absolute and always POSIX-normalised.
|
|
78
|
+
*/
|
|
79
|
+
export function resolveRoot(explicitRoot, cwd = process.cwd()) {
|
|
80
|
+
if (explicitRoot !== undefined && explicitRoot !== '') {
|
|
81
|
+
return toPosix(path.resolve(explicitRoot));
|
|
82
|
+
}
|
|
83
|
+
const found = findGitRoot(cwd) ?? findManifestDir(cwd) ?? cwd;
|
|
84
|
+
return toPosix(path.resolve(found));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* `/Users/theo/dev/app/src/App.tsx` + root `/Users/theo/dev/app` →
|
|
88
|
+
* `src/App.tsx`.
|
|
89
|
+
*
|
|
90
|
+
* A file outside the root keeps its `../` prefix rather than being silently
|
|
91
|
+
* rewritten — `root + '/' + '../lib/x.tsx'` still resolves to the right file,
|
|
92
|
+
* and inventing a path is the one failure mode doc 00 §3 forbids outright.
|
|
93
|
+
*/
|
|
94
|
+
export function toRepoRelative(absoluteFile, root) {
|
|
95
|
+
const abs = path.resolve(absoluteFile);
|
|
96
|
+
const rel = toPosix(path.relative(root, abs));
|
|
97
|
+
if (rel === '')
|
|
98
|
+
return toPosix(path.basename(abs));
|
|
99
|
+
return rel.startsWith('./') ? rel.slice(2) : rel;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=root.js.map
|
package/dist/root.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root.js","sourceRoot":"","sources":["../src/root.ts"],"names":[],"mappings":"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"}
|
package/dist/rspack.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rspack.d.ts","sourceRoot":"","sources":["../src/rspack.ts"],"names":[],"mappings":";AAUA,wBAAmD"}
|
package/dist/rspack.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ```js
|
|
3
|
+
* // rspack.config.js
|
|
4
|
+
* import snagAnchor from '@frmwrkd/snag-anchor/rspack';
|
|
5
|
+
* export default { plugins: [snagAnchor()] };
|
|
6
|
+
* ```
|
|
7
|
+
*/
|
|
8
|
+
import { createRspackPlugin } from 'unplugin';
|
|
9
|
+
import { unpluginFactory } from './index.js';
|
|
10
|
+
export default createRspackPlugin(unpluginFactory);
|
|
11
|
+
//# sourceMappingURL=rspack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rspack.js","sourceRoot":"","sources":["../src/rspack.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,eAAe,kBAAkB,CAAC,eAAe,CAAC,CAAC"}
|
package/dist/stamp.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a stamp *is*, independent of the language being stamped.
|
|
3
|
+
*
|
|
4
|
+
* doc 02 §2.1 — two attributes only, self-contained, no global registry:
|
|
5
|
+
*
|
|
6
|
+
* ```html
|
|
7
|
+
* <button data-snag="src/checkout/PromoField.tsx:42:7" data-snag-cmp="PromoField">
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* LocatorJS's default `data-locatorjs-id` mode requires a
|
|
11
|
+
* `window.__LOCATOR_DATA__` lookup table. Do not copy that: only `data-*`
|
|
12
|
+
* attributes cross the ISOLATED-world boundary, so an indirection through a
|
|
13
|
+
* page global would cost the resolver a `postMessage` round trip and would
|
|
14
|
+
* throw tier 0's entire advantage away.
|
|
15
|
+
*
|
|
16
|
+
* The JSX path (`babel/plugin.ts`) and the Vue path (`vue/transform.ts`) share
|
|
17
|
+
* this module so the emitted attribute set cannot diverge between frameworks.
|
|
18
|
+
*/
|
|
19
|
+
import type { MagicString } from './magic-string.js';
|
|
20
|
+
import type { AnchorMode } from './options.js';
|
|
21
|
+
/** Whatever `MagicString#generateMap` produces — a standard v3 source map. */
|
|
22
|
+
export type AnchorSourceMap = ReturnType<MagicString['generateMap']>;
|
|
23
|
+
export interface StampContext {
|
|
24
|
+
/** Repo-relative, POSIX-normalised path of the file being transformed. */
|
|
25
|
+
file: string;
|
|
26
|
+
attribute: string;
|
|
27
|
+
componentAttribute: string;
|
|
28
|
+
intrinsicOnly: boolean;
|
|
29
|
+
mode: AnchorMode;
|
|
30
|
+
}
|
|
31
|
+
export interface AnchorAttribute {
|
|
32
|
+
name: string;
|
|
33
|
+
value: string;
|
|
34
|
+
}
|
|
35
|
+
/** One element that will be stamped, with everything needed to verify it. */
|
|
36
|
+
export interface StampSite {
|
|
37
|
+
/** Offset in the *original* source where the attribute text is inserted. */
|
|
38
|
+
offset: number;
|
|
39
|
+
/** 1-based line of the `<` of the opening tag. */
|
|
40
|
+
line: number;
|
|
41
|
+
/** 1-based column of the `<` of the opening tag. */
|
|
42
|
+
col: number;
|
|
43
|
+
/** Tag exactly as written in the source, e.g. `div`, `my-widget`. */
|
|
44
|
+
tag: string;
|
|
45
|
+
component: string | undefined;
|
|
46
|
+
attributes: AnchorAttribute[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* `'minimal'` emits the component name and **no path** — doc 02 §2.3. With no
|
|
50
|
+
* resolvable component name it therefore emits nothing at all, which is the
|
|
51
|
+
* point: a staging build should leak a name or nothing, never a directory tree.
|
|
52
|
+
*/
|
|
53
|
+
export declare function buildAttributes(site: {
|
|
54
|
+
line: number;
|
|
55
|
+
col: number;
|
|
56
|
+
component: string | undefined;
|
|
57
|
+
}, ctx: StampContext): AnchorAttribute[];
|
|
58
|
+
/**
|
|
59
|
+
* JSX string-literal attribute values and HTML attribute values are both
|
|
60
|
+
* entity-decoded by their parsers, so `&` and `"` have to be escaped on the way
|
|
61
|
+
* in or a path containing either would come back out different.
|
|
62
|
+
*/
|
|
63
|
+
export declare function escapeAttributeValue(value: string): string;
|
|
64
|
+
/** ` data-snag="…" data-snag-cmp="…"` — the exact text spliced into the source. */
|
|
65
|
+
export declare function renderAttributes(attributes: readonly AnchorAttribute[]): string;
|
|
66
|
+
//# sourceMappingURL=stamp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stamp.d.ts","sourceRoot":"","sources":["../src/stamp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,8EAA8E;AAC9E,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC;AAErE,MAAM,WAAW,YAAY;IAC3B,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,6EAA6E;AAC7E,MAAM,WAAW,SAAS;IACxB,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,UAAU,EAAE,eAAe,EAAE,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,EAClE,GAAG,EAAE,YAAY,GAChB,eAAe,EAAE,CAWnB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,GAAG,MAAM,CAE/E"}
|
package/dist/stamp.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `'minimal'` emits the component name and **no path** — doc 02 §2.3. With no
|
|
3
|
+
* resolvable component name it therefore emits nothing at all, which is the
|
|
4
|
+
* point: a staging build should leak a name or nothing, never a directory tree.
|
|
5
|
+
*/
|
|
6
|
+
export function buildAttributes(site, ctx) {
|
|
7
|
+
const attributes = [];
|
|
8
|
+
if (ctx.mode === 'full') {
|
|
9
|
+
attributes.push({ name: ctx.attribute, value: `${ctx.file}:${site.line}:${site.col}` });
|
|
10
|
+
}
|
|
11
|
+
if (site.component !== undefined && site.component !== '') {
|
|
12
|
+
attributes.push({ name: ctx.componentAttribute, value: site.component });
|
|
13
|
+
}
|
|
14
|
+
return attributes;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* JSX string-literal attribute values and HTML attribute values are both
|
|
18
|
+
* entity-decoded by their parsers, so `&` and `"` have to be escaped on the way
|
|
19
|
+
* in or a path containing either would come back out different.
|
|
20
|
+
*/
|
|
21
|
+
export function escapeAttributeValue(value) {
|
|
22
|
+
return value.replace(/&/g, '&').replace(/"/g, '"');
|
|
23
|
+
}
|
|
24
|
+
/** ` data-snag="…" data-snag-cmp="…"` — the exact text spliced into the source. */
|
|
25
|
+
export function renderAttributes(attributes) {
|
|
26
|
+
return attributes.map((attr) => ` ${attr.name}="${escapeAttributeValue(attr.value)}"`).join('');
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=stamp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stamp.js","sourceRoot":"","sources":["../src/stamp.ts"],"names":[],"mappings":"AAoDA;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAkE,EAClE,GAAiB;IAEjB,MAAM,UAAU,GAAsB,EAAE,CAAC;IAEzC,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACxB,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAC1F,CAAC;IACD,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,IAAI,CAAC,SAAS,KAAK,EAAE,EAAE,CAAC;QAC1D,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,kBAAkB,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC9D,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,gBAAgB,CAAC,UAAsC;IACrE,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAClG,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ResolvedAnchorOptions } from './options.js';
|
|
2
|
+
import type { AnchorSourceMap, StampContext, StampSite } from './stamp.js';
|
|
3
|
+
export interface AnchorTransformResult {
|
|
4
|
+
code: string;
|
|
5
|
+
map: AnchorSourceMap;
|
|
6
|
+
sites: StampSite[];
|
|
7
|
+
}
|
|
8
|
+
export declare function stampContextFor(id: string, options: ResolvedAnchorOptions): StampContext | null;
|
|
9
|
+
/**
|
|
10
|
+
* `null` means "leave this file exactly as it was" — not included, not a
|
|
11
|
+
* language we stamp, nothing to stamp, or unparseable. Never throws.
|
|
12
|
+
*/
|
|
13
|
+
export declare function transformSource(code: string, id: string, options: ResolvedAnchorOptions): AnchorTransformResult | null;
|
|
14
|
+
//# sourceMappingURL=transform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE1D,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG3E,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,eAAe,CAAC;IACrB,KAAK,EAAE,SAAS,EAAE,CAAC;CACpB;AAKD,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB,GAAG,YAAY,GAAG,IAAI,CAW/F;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,qBAAqB,GAC7B,qBAAqB,GAAG,IAAI,CA0B9B"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One entry point that every bundler — and the Turbopack/webpack loader —
|
|
3
|
+
* routes through, so the JSX path and the Vue path can never be wired up
|
|
4
|
+
* differently by two different integrations.
|
|
5
|
+
*/
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { stampJsx } from './babel/plugin.js';
|
|
8
|
+
import { normaliseId } from './options.js';
|
|
9
|
+
import { toRepoRelative } from './root.js';
|
|
10
|
+
import { stampVue } from './vue/transform.js';
|
|
11
|
+
/** Extensions the JSX path knows how to parse. */
|
|
12
|
+
const JSX_EXTENSIONS = new Set(['.jsx', '.tsx', '.js', '.mjs', '.cjs', '.ts', '.mts', '.cts']);
|
|
13
|
+
export function stampContextFor(id, options) {
|
|
14
|
+
const file = normaliseId(id);
|
|
15
|
+
if (file === null)
|
|
16
|
+
return null;
|
|
17
|
+
return {
|
|
18
|
+
file: toRepoRelative(file, options.root),
|
|
19
|
+
attribute: options.attribute,
|
|
20
|
+
componentAttribute: options.componentAttribute,
|
|
21
|
+
intrinsicOnly: options.intrinsicOnly,
|
|
22
|
+
mode: options.mode,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* `null` means "leave this file exactly as it was" — not included, not a
|
|
27
|
+
* language we stamp, nothing to stamp, or unparseable. Never throws.
|
|
28
|
+
*/
|
|
29
|
+
export function transformSource(code, id, options) {
|
|
30
|
+
if (options.mode === 'off')
|
|
31
|
+
return null;
|
|
32
|
+
if (!options.filter(id))
|
|
33
|
+
return null;
|
|
34
|
+
const file = normaliseId(id);
|
|
35
|
+
if (file === null)
|
|
36
|
+
return null;
|
|
37
|
+
const ctx = stampContextFor(id, options);
|
|
38
|
+
if (ctx === null)
|
|
39
|
+
return null;
|
|
40
|
+
const ext = path.posix.extname(file).toLowerCase();
|
|
41
|
+
if (ext === '.vue') {
|
|
42
|
+
return stampVue(code, ctx, file);
|
|
43
|
+
}
|
|
44
|
+
// Svelte 5 already attaches `element.__svelte_meta`; resolver tier 1 reads it
|
|
45
|
+
// straight off the node. Injecting here would duplicate framework metadata
|
|
46
|
+
// that can only disagree with it. doc 02 §2.2.
|
|
47
|
+
if (ext === '.svelte')
|
|
48
|
+
return null;
|
|
49
|
+
if (JSX_EXTENSIONS.has(ext)) {
|
|
50
|
+
return stampJsx(code, ctx, file);
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=transform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform.js","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAQ9C,kDAAkD;AAClD,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAE/F,MAAM,UAAU,eAAe,CAAC,EAAU,EAAE,OAA8B;IACxE,MAAM,IAAI,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC7B,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAE/B,OAAO;QACL,IAAI,EAAE,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC;QACxC,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;QAC9C,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,IAAI,EAAE,OAAO,CAAC,IAAI;KACnB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAY,EACZ,EAAU,EACV,OAA8B;IAE9B,IAAI,OAAO,CAAC,IAAI,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IACxC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IAErC,MAAM,IAAI,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC7B,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAE/B,MAAM,GAAG,GAAG,eAAe,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IACzC,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAE9B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IAEnD,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;QACnB,OAAO,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,8EAA8E;IAC9E,2EAA2E;IAC3E,+CAA+C;IAC/C,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAEnC,IAAI,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":";AAUA,wBAAiD"}
|
package/dist/vite.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ```ts
|
|
3
|
+
* // vite.config.ts
|
|
4
|
+
* import snagAnchor from '@frmwrkd/snag-anchor/vite';
|
|
5
|
+
* export default defineConfig({ plugins: [snagAnchor()] });
|
|
6
|
+
* ```
|
|
7
|
+
*/
|
|
8
|
+
import { createVitePlugin } from 'unplugin';
|
|
9
|
+
import { unpluginFactory } from './index.js';
|
|
10
|
+
export default createVitePlugin(unpluginFactory);
|
|
11
|
+
//# sourceMappingURL=vite.js.map
|
package/dist/vite.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite.js","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,eAAe,gBAAgB,CAAC,eAAe,CAAC,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { AnchorSourceMap, StampContext, StampSite } from '../stamp.js';
|
|
2
|
+
interface VuePosition {
|
|
3
|
+
line: number;
|
|
4
|
+
column: number;
|
|
5
|
+
offset: number;
|
|
6
|
+
}
|
|
7
|
+
interface VueLocation {
|
|
8
|
+
start: VuePosition;
|
|
9
|
+
end: VuePosition;
|
|
10
|
+
}
|
|
11
|
+
interface VueProp {
|
|
12
|
+
type: number;
|
|
13
|
+
name: string;
|
|
14
|
+
loc: VueLocation;
|
|
15
|
+
/** Directives only: `v-bind:data-snag` → `arg.content === 'data-snag'`. */
|
|
16
|
+
arg?: {
|
|
17
|
+
content?: string;
|
|
18
|
+
isStatic?: boolean;
|
|
19
|
+
} | undefined;
|
|
20
|
+
}
|
|
21
|
+
interface VueNode {
|
|
22
|
+
type: number;
|
|
23
|
+
tag?: string;
|
|
24
|
+
tagType?: number;
|
|
25
|
+
props?: VueProp[];
|
|
26
|
+
children?: VueNode[];
|
|
27
|
+
loc: VueLocation;
|
|
28
|
+
}
|
|
29
|
+
export interface VueStampResult {
|
|
30
|
+
code: string;
|
|
31
|
+
map: AnchorSourceMap;
|
|
32
|
+
sites: StampSite[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The component name for an SFC is the file's base name — `App.vue` → `App`.
|
|
36
|
+
*
|
|
37
|
+
* An SFC *is* a component, so unlike JSX there is no enclosing scope to walk;
|
|
38
|
+
* this is the same convention Vue itself uses to infer a component's name when
|
|
39
|
+
* none is declared.
|
|
40
|
+
*/
|
|
41
|
+
export declare function sfcComponentName(file: string): string | undefined;
|
|
42
|
+
export declare function collectVueStampSites(root: VueNode, ctx: StampContext): StampSite[];
|
|
43
|
+
/**
|
|
44
|
+
* Returns `null` when there is nothing to do — no template, a non-HTML template
|
|
45
|
+
* language, a parse failure, or no stampable element. Like the JSX path, this
|
|
46
|
+
* never throws: a broken dev server costs far more than a missing anchor.
|
|
47
|
+
*/
|
|
48
|
+
export declare function stampVue(code: string, ctx: StampContext, filename?: string): VueStampResult | null;
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=transform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../src/vue/transform.ts"],"names":[],"mappings":"AAwCA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAuB5E,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,WAAW;IACnB,KAAK,EAAE,WAAW,CAAC;IACnB,GAAG,EAAE,WAAW,CAAC;CAClB;AAED,UAAU,OAAO;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,WAAW,CAAC;IACjB,2EAA2E;IAC3E,GAAG,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAC;CAC5D;AAED,UAAU,OAAO;IACf,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB,GAAG,EAAE,WAAW,CAAC;CAClB;AAID,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,eAAe,CAAC;IACrB,KAAK,EAAE,SAAS,EAAE,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIjE;AA8CD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,YAAY,GAAG,SAAS,EAAE,CA2BlF;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAmClG"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vue SFC template transform — the same two attributes, from the same
|
|
3
|
+
* `StampContext`, so a Vue app and a React app hand the resolver identical
|
|
4
|
+
* strings (doc 02 §2.2).
|
|
5
|
+
*
|
|
6
|
+
* ## Why the SFC source is spliced rather than the render function patched
|
|
7
|
+
*
|
|
8
|
+
* `@vue/compiler-sfc`'s `parse()` hands back `descriptor.template.ast`, whose
|
|
9
|
+
* `loc.start.offset` / `.line` / `.column` are **absolute within the whole
|
|
10
|
+
* `.vue` file**, not relative to the template block. That is exactly what tier
|
|
11
|
+
* 0 needs — the line an editor opens is the line in `App.vue`, counted from the
|
|
12
|
+
* top of `App.vue` including the `<script>` block above the template. So this
|
|
13
|
+
* runs `enforce: 'pre'`, inserts attributes into the raw SFC text, and hands
|
|
14
|
+
* the modified SFC to `@vitejs/plugin-vue` as if the developer had typed it.
|
|
15
|
+
*
|
|
16
|
+
* ## What is deliberately not stamped
|
|
17
|
+
*
|
|
18
|
+
* `tagType` distinguishes the four kinds of tag in a Vue template
|
|
19
|
+
* (`ElementTypes` in `@vue/compiler-core`):
|
|
20
|
+
*
|
|
21
|
+
* 0 ELEMENT — a real host element. Stamped.
|
|
22
|
+
* 1 COMPONENT — `<MyChild/>`. Not stamped, for the same reason
|
|
23
|
+
* `<PromoField/>` is not stamped in JSX: unless the child
|
|
24
|
+
* happens to land the fallthrough attribute on a DOM node, the
|
|
25
|
+
* attribute goes nowhere. `intrinsicOnly: false` opts in.
|
|
26
|
+
* 2 SLOT — `<slot/>`. A compiler construct, never a DOM node.
|
|
27
|
+
* 3 TEMPLATE — `<template v-if>`. Also never a DOM node.
|
|
28
|
+
*
|
|
29
|
+
* ## Svelte
|
|
30
|
+
*
|
|
31
|
+
* There is deliberately no Svelte equivalent of this file. Svelte 5 already
|
|
32
|
+
* attaches `element.__svelte_meta = { loc: { file, line, column } }` in dev and
|
|
33
|
+
* resolver tier 1 reads it straight off the node (doc 03 §2.2 technique #3,
|
|
34
|
+
* "setup: **zero**"). `.svelte` is therefore absent from the default `include`.
|
|
35
|
+
*/
|
|
36
|
+
import { parse as parseSfc } from '@vue/compiler-sfc';
|
|
37
|
+
import path from 'node:path';
|
|
38
|
+
import { MagicString } from '../magic-string.js';
|
|
39
|
+
import { buildAttributes, renderAttributes } from '../stamp.js';
|
|
40
|
+
/* ------------------------------------------------------------------ AST types
|
|
41
|
+
*
|
|
42
|
+
* `@vue/compiler-core` is not a direct dependency of this package (it arrives
|
|
43
|
+
* only as a transitive dependency of `@vue/compiler-sfc`, and its types are not
|
|
44
|
+
* re-exported), so the handful of node shapes this file touches are declared
|
|
45
|
+
* structurally. The numeric constants below are `NodeTypes` / `ElementTypes`
|
|
46
|
+
* from `@vue/compiler-core`, verified against the parser's actual output.
|
|
47
|
+
*/
|
|
48
|
+
/** `NodeTypes.ELEMENT` */
|
|
49
|
+
const NODE_TYPE_ELEMENT = 1;
|
|
50
|
+
/** `NodeTypes.ATTRIBUTE` */
|
|
51
|
+
const NODE_TYPE_ATTRIBUTE = 6;
|
|
52
|
+
/** `NodeTypes.DIRECTIVE` */
|
|
53
|
+
const NODE_TYPE_DIRECTIVE = 7;
|
|
54
|
+
/** `ElementTypes.ELEMENT` — a host element, the only kind that reaches the DOM as itself. */
|
|
55
|
+
const ELEMENT_TYPE_ELEMENT = 0;
|
|
56
|
+
/** `ElementTypes.COMPONENT` */
|
|
57
|
+
const ELEMENT_TYPE_COMPONENT = 1;
|
|
58
|
+
/**
|
|
59
|
+
* The component name for an SFC is the file's base name — `App.vue` → `App`.
|
|
60
|
+
*
|
|
61
|
+
* An SFC *is* a component, so unlike JSX there is no enclosing scope to walk;
|
|
62
|
+
* this is the same convention Vue itself uses to infer a component's name when
|
|
63
|
+
* none is declared.
|
|
64
|
+
*/
|
|
65
|
+
export function sfcComponentName(file) {
|
|
66
|
+
const base = path.posix.basename(file.replace(/\\/g, '/'));
|
|
67
|
+
const name = base.replace(/\.vue$/i, '');
|
|
68
|
+
return name === '' ? undefined : name;
|
|
69
|
+
}
|
|
70
|
+
function hasAttribute(node, attribute) {
|
|
71
|
+
const props = node.props ?? [];
|
|
72
|
+
return props.some((prop) => {
|
|
73
|
+
if (prop.type === NODE_TYPE_ATTRIBUTE)
|
|
74
|
+
return prop.name === attribute;
|
|
75
|
+
if (prop.type === NODE_TYPE_DIRECTIVE) {
|
|
76
|
+
// `:data-snag="…"` / `v-bind:data-snag="…"` is still a user override.
|
|
77
|
+
return prop.name === 'bind' && prop.arg?.content === attribute;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function shouldStampVue(node, ctx) {
|
|
83
|
+
if (ctx.mode === 'off')
|
|
84
|
+
return false;
|
|
85
|
+
if (node.type !== NODE_TYPE_ELEMENT)
|
|
86
|
+
return false;
|
|
87
|
+
if (typeof node.tag !== 'string' || node.tag === '')
|
|
88
|
+
return false;
|
|
89
|
+
const tagType = node.tagType ?? ELEMENT_TYPE_ELEMENT;
|
|
90
|
+
if (tagType === ELEMENT_TYPE_ELEMENT) {
|
|
91
|
+
// always stamped
|
|
92
|
+
}
|
|
93
|
+
else if (tagType === ELEMENT_TYPE_COMPONENT) {
|
|
94
|
+
if (ctx.intrinsicOnly)
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// SLOT and TEMPLATE never reach the DOM.
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
if (hasAttribute(node, ctx.attribute))
|
|
102
|
+
return false;
|
|
103
|
+
if (hasAttribute(node, ctx.componentAttribute))
|
|
104
|
+
return false;
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Insert after the last existing prop, or straight after the tag name when
|
|
109
|
+
* there are none — the same rule the JSX path uses, so a `v-bind="obj"` spread
|
|
110
|
+
* cannot overwrite the stamp.
|
|
111
|
+
*/
|
|
112
|
+
function insertionOffset(node) {
|
|
113
|
+
const props = node.props ?? [];
|
|
114
|
+
const last = props.length > 0 ? props[props.length - 1] : undefined;
|
|
115
|
+
if (last !== undefined)
|
|
116
|
+
return last.loc.end.offset;
|
|
117
|
+
return node.loc.start.offset + 1 + (node.tag ?? '').length;
|
|
118
|
+
}
|
|
119
|
+
export function collectVueStampSites(root, ctx) {
|
|
120
|
+
const sites = [];
|
|
121
|
+
const component = sfcComponentName(ctx.file);
|
|
122
|
+
const step = (node) => {
|
|
123
|
+
if (shouldStampVue(node, ctx)) {
|
|
124
|
+
const line = node.loc.start.line;
|
|
125
|
+
// Vue's parser already reports 1-based columns; Babel's are 0-based and
|
|
126
|
+
// get +1 in `babel/plugin.ts`. Both paths emit 1-based.
|
|
127
|
+
const col = node.loc.start.column;
|
|
128
|
+
const attributes = buildAttributes({ line, col, component }, ctx);
|
|
129
|
+
if (attributes.length > 0) {
|
|
130
|
+
sites.push({
|
|
131
|
+
offset: insertionOffset(node),
|
|
132
|
+
line,
|
|
133
|
+
col,
|
|
134
|
+
tag: node.tag ?? '',
|
|
135
|
+
component,
|
|
136
|
+
attributes,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
for (const child of node.children ?? [])
|
|
141
|
+
step(child);
|
|
142
|
+
};
|
|
143
|
+
step(root);
|
|
144
|
+
return sites;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Returns `null` when there is nothing to do — no template, a non-HTML template
|
|
148
|
+
* language, a parse failure, or no stampable element. Like the JSX path, this
|
|
149
|
+
* never throws: a broken dev server costs far more than a missing anchor.
|
|
150
|
+
*/
|
|
151
|
+
export function stampVue(code, ctx, filename) {
|
|
152
|
+
if (ctx.mode === 'off')
|
|
153
|
+
return null;
|
|
154
|
+
const name = filename ?? ctx.file;
|
|
155
|
+
let template;
|
|
156
|
+
try {
|
|
157
|
+
const parsed = parseSfc(code, { filename: name });
|
|
158
|
+
template = parsed.descriptor.template;
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
if (template === null || template === undefined)
|
|
164
|
+
return null;
|
|
165
|
+
// `lang="pug"` and friends: the template block is not HTML, so offsets into
|
|
166
|
+
// it would splice attributes into the middle of somebody's indentation.
|
|
167
|
+
if (template.lang !== undefined && template.lang !== '' && template.lang.toLowerCase() !== 'html') {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
// `<template src="./other.html">` — nothing of ours lives in this file.
|
|
171
|
+
if (template.src !== undefined && template.src !== '')
|
|
172
|
+
return null;
|
|
173
|
+
if (template.ast === undefined || template.ast === null)
|
|
174
|
+
return null;
|
|
175
|
+
const sites = collectVueStampSites(template.ast, ctx);
|
|
176
|
+
if (sites.length === 0)
|
|
177
|
+
return null;
|
|
178
|
+
const s = new MagicString(code);
|
|
179
|
+
for (const site of sites) {
|
|
180
|
+
s.appendLeft(site.offset, renderAttributes(site.attributes));
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
code: s.toString(),
|
|
184
|
+
map: s.generateMap({ hires: true, source: name, includeContent: false }),
|
|
185
|
+
sites,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=transform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform.js","sourceRoot":"","sources":["../../src/vue/transform.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webpack.d.ts","sourceRoot":"","sources":["../src/webpack.ts"],"names":[],"mappings":";AAcA,wBAAoD"}
|
package/dist/webpack.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ```js
|
|
3
|
+
* // webpack.config.js
|
|
4
|
+
* const snagAnchor = require('@frmwrkd/snag-anchor/webpack').default;
|
|
5
|
+
* module.exports = { plugins: [snagAnchor()] };
|
|
6
|
+
* ```
|
|
7
|
+
*
|
|
8
|
+
* Note this is the *plugin*, not the loader. Turbopack's `turbopack.rules` maps
|
|
9
|
+
* extensions to webpack-shaped **loaders**, not to plugins — use
|
|
10
|
+
* `@frmwrkd/snag-anchor/loader` there. See `src/loader.cts`.
|
|
11
|
+
*/
|
|
12
|
+
import { createWebpackPlugin } from 'unplugin';
|
|
13
|
+
import { unpluginFactory } from './index.js';
|
|
14
|
+
export default createWebpackPlugin(unpluginFactory);
|
|
15
|
+
//# sourceMappingURL=webpack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webpack.js","sourceRoot":"","sources":["../src/webpack.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,eAAe,mBAAmB,CAAC,eAAe,CAAC,CAAC"}
|