vue-shaker 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/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +157 -0
- package/dist/index.cjs +1 -0
- package/dist/index.js +1 -0
- package/dist/scan.js +1 -0
- package/dist/types/analyze.d.ts +82 -0
- package/dist/types/css.d.ts +20 -0
- package/dist/types/dead.d.ts +61 -0
- package/dist/types/eval.d.ts +30 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/ir.d.ts +111 -0
- package/dist/types/parse.d.ts +165 -0
- package/dist/types/scan.d.ts +12 -0
- package/dist/types/transform.d.ts +24 -0
- package/dist/types/vite.d.ts +25 -0
- package/dist/vite.js +1 -0
- package/package.json +80 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# vue-shaker
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Initial release.
|
|
8
|
+
|
|
9
|
+
A sound, source-level tree-shaker for Vue 3 SFCs that use `<script setup>` +
|
|
10
|
+
`defineProps`. It runs in `vite build`, before the Vue compiler, and slims each
|
|
11
|
+
`.vue` by partially evaluating it against how the whole app uses it:
|
|
12
|
+
|
|
13
|
+
- **L0/L1** — props no call site passes (or always passes the same constant) are
|
|
14
|
+
dropped from `defineProps`, demoted to a local `const`, and their attribute is
|
|
15
|
+
stripped at every call site.
|
|
16
|
+
- **L1.5** — value-set narrowing deletes provably-dead `v-if`/`v-else-if` arms.
|
|
17
|
+
- **CSS** — unreachable `<style scoped>` rules are removed (Vue does no
|
|
18
|
+
unused-CSS pruning itself, so the shaker owns it).
|
|
19
|
+
|
|
20
|
+
Ships a Vite plugin (`vue-shaker/vite`) and a Rollup plugin
|
|
21
|
+
(`rollup-plugin-vue-shaker`). Build-only by design — dev is a pass-through.
|
|
22
|
+
Soundness is defended by a differential-SSR oracle. Requires `vue@^3`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Yuichiro Yamashita
|
|
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,157 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/baseballyama/vue-shaker/main/assets/vue-shaker.png" alt="vue-shaker" width="190" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">vue-shaker</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">A <strong>sound, source-level tree-shaker for Vue 3 SFCs (<code><script setup></code> + <code>defineProps</code>).</strong></p>
|
|
8
|
+
|
|
9
|
+
**▶ Try it in the browser: https://baseballyama.github.io/vue-shaker/** — an
|
|
10
|
+
interactive playground that runs the engine entirely client-side.
|
|
11
|
+
|
|
12
|
+
It runs in your app's production build, _before_ the Vue compiler, and slims each
|
|
13
|
+
`.vue` file by partially evaluating it against how the **whole app** actually uses
|
|
14
|
+
it: props that are never passed (or always passed the same value) are folded to
|
|
15
|
+
their constant, the dead `v-if` arms behind them are deleted, those props are
|
|
16
|
+
dropped from the `defineProps` signature, and the now-pointless attributes are
|
|
17
|
+
removed at every call site. The Vue compiler then only sees the code your app can
|
|
18
|
+
actually reach.
|
|
19
|
+
|
|
20
|
+
It is **sound first**: it never changes what the user sees. When it cannot prove a
|
|
21
|
+
transform is safe, it leaves the code untouched (bails).
|
|
22
|
+
|
|
23
|
+
See [`docs/ARCHITECTURE.md`](https://github.com/baseballyama/vue-shaker/blob/main/docs/ARCHITECTURE.md) for the full design.
|
|
24
|
+
|
|
25
|
+
## Why this exists (and why a JS bundler can't do it)
|
|
26
|
+
|
|
27
|
+
Design-system components carry lots of props (`Button` with
|
|
28
|
+
`variant / size / loading / icon / iconPosition / fullWidth / rounded / href …`),
|
|
29
|
+
but any one app uses only a few. The code behind the unused props — template
|
|
30
|
+
branches, class computation, computed values, imports, CSS — is effectively dead
|
|
31
|
+
for that app, yet it ships anyway.
|
|
32
|
+
|
|
33
|
+
It cannot be removed _after_ Vue compiles, because Vue emits **one generic render
|
|
34
|
+
function per component**, shared by every caller. In that output the prop values
|
|
35
|
+
flow through the runtime (`props.loading`, `$props`), so `loading` / `variant` are
|
|
36
|
+
not static JS constants — terser/esbuild/Rollup cannot fold `if (loading)` to
|
|
37
|
+
`if (false)`, and the single module has no whole-program information to know which
|
|
38
|
+
props this particular app never uses.
|
|
39
|
+
|
|
40
|
+
vue-shaker works **one step earlier**, on the pre-compile `.vue` source, where the
|
|
41
|
+
prop's value (its default, or the literal at the call site) is still visible and
|
|
42
|
+
the template structure is intact. It is essentially a **whole-program partial
|
|
43
|
+
evaluator + dead-code eliminator that understands Vue**, driven by every call site
|
|
44
|
+
in the app.
|
|
45
|
+
|
|
46
|
+
### The CSS differentiator (what a bundler genuinely can't reach)
|
|
47
|
+
|
|
48
|
+
Given `:class="['btn', \`btn-${variant}\`]"` where the app only ever passes
|
|
49
|
+
`variant ∈ {primary, secondary}`, the class `btn-danger` can never exist at
|
|
50
|
+
runtime. But the class only appears as a runtime string, so Rollup/terser can't
|
|
51
|
+
touch it (the class isn't in the JS at all), and — unlike Svelte — **Vue does not
|
|
52
|
+
prune unused `<style scoped>` rules at all**. So `btn-danger` ships no matter what.
|
|
53
|
+
|
|
54
|
+
vue-shaker computes the reachable value set of `variant`, proves the `.btn-danger`
|
|
55
|
+
/ `.btn-ghost` rules can never match any element this component renders, and
|
|
56
|
+
**removes those `<style scoped>` rules** — while keeping `.btn`, `.btn-primary`,
|
|
57
|
+
`.btn-secondary`. Because Vue has no unused-CSS pruning of its own, this is an even
|
|
58
|
+
bigger win for Vue than for Svelte.
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
pnpm add -D vue-shaker
|
|
64
|
+
# or: npm i -D vue-shaker / yarn add -D vue-shaker
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Requires `vue@^3`.
|
|
68
|
+
|
|
69
|
+
## Usage (Vite)
|
|
70
|
+
|
|
71
|
+
Add the plugin **before** `vue()` so it hands already-slimmed source to the Vue
|
|
72
|
+
compiler. It is **build-only by design** — dev is a pass-through.
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
// vite.config.ts
|
|
76
|
+
import { defineConfig } from 'vite';
|
|
77
|
+
import vue from '@vitejs/plugin-vue';
|
|
78
|
+
import { shaker } from 'vue-shaker/vite';
|
|
79
|
+
|
|
80
|
+
export default defineConfig({
|
|
81
|
+
plugins: [
|
|
82
|
+
// `include` must cover EVERY call site in the app, or prop elimination
|
|
83
|
+
// would be unsound. Defaults to the Vite root.
|
|
84
|
+
shaker({ include: ['src'] }),
|
|
85
|
+
vue(),
|
|
86
|
+
],
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
There is also a plain-Rollup plugin (`rollup-plugin-vue-shaker`) for non-Vite
|
|
91
|
+
pipelines; the Vite plugin is preferred for apps.
|
|
92
|
+
|
|
93
|
+
### Options
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
shaker({
|
|
97
|
+
include: ['src'], // dirs (relative to root) holding every .vue call site
|
|
98
|
+
level: 1, // 0 | 1 | 2 — default 1 (L0/L1/L1.5 always on). 2 = opt-in L2.
|
|
99
|
+
monomorphize: false, // L2 tuning; only consulted when level: 2.
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## What it does
|
|
104
|
+
|
|
105
|
+
| Level | What it removes | Default |
|
|
106
|
+
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
|
|
107
|
+
| **L0** | Props no call site ever passes → fold to the default, drop from `defineProps`, strip the attribute at call sites | on |
|
|
108
|
+
| **L1** | Props that collapse to one constant app-wide → fold + drop + strip every call site's attribute | on |
|
|
109
|
+
| **L1.5** | Value-set **narrowing**: with `variant ∈ {primary, secondary}`, delete provably-dead `v-if`/`v-else-if` arms (prop stays in the signature) | on |
|
|
110
|
+
| **CSS** | `<style scoped>` rules whose class can never be produced given the value sets — the bundler-can't differentiator (and Vue can't either) | on |
|
|
111
|
+
| **L2** | Per-call-site monomorphization: specialize a component per prop shape (deduped by residual, capped by `maxVariants`) | **opt-in** |
|
|
112
|
+
|
|
113
|
+
## Soundness
|
|
114
|
+
|
|
115
|
+
The whole point is to **never change observable behavior**.
|
|
116
|
+
|
|
117
|
+
- **Differential-SSR verified.** Tests server-render the original and the shaken
|
|
118
|
+
component (via `@vue/server-renderer`, comments/scope-ids stripped, whitespace
|
|
119
|
+
normalized) and assert the HTML is identical for every value the app passes.
|
|
120
|
+
- **Conservative bail.** When a transform can't be _proven_ safe, the code is left
|
|
121
|
+
as-is. Whole-component bails: a component rendered through `<component :is>`, that
|
|
122
|
+
escapes as a value, or is reached only through a barrel/named re-export (its call
|
|
123
|
+
sites aren't enumerable). Per-prop bails: a `v-bind="…"` spread that could
|
|
124
|
+
overwrite it, a name shadowed by `v-for` / scoped-slot props, or used in a way the
|
|
125
|
+
engine can't follow.
|
|
126
|
+
- **Side effects preserved.** A call-site attribute is only stripped if its value
|
|
127
|
+
has no side effects; a value's code is removed only when it is provably pure and
|
|
128
|
+
unused.
|
|
129
|
+
- **Whole-program fixpoint.** Call sites inside a folded-away `v-if` don't count
|
|
130
|
+
toward a child's prop profile; analysis iterates to a fixpoint so cascades are
|
|
131
|
+
consistent with what the transform actually deletes.
|
|
132
|
+
|
|
133
|
+
## Limitations
|
|
134
|
+
|
|
135
|
+
- **`<script setup>` + `defineProps` only.** Options API and plain `<script>`
|
|
136
|
+
(`defineComponent({ props })`) are out of scope and silently passed through.
|
|
137
|
+
- **Needs `.vue` source.** Libraries shipping compiled JS can't be shaken (the
|
|
138
|
+
source has to be visible — that's the whole premise). Anything it can't resolve is
|
|
139
|
+
silently passed through.
|
|
140
|
+
- **Build only.** It runs in `vite build`, not in dev/HMR — whole-program analysis
|
|
141
|
+
is fundamentally incompatible with HMR's locality. Dev is always a pass-through
|
|
142
|
+
(unoptimized but always correct).
|
|
143
|
+
- **`include` must cover the whole app.** A call site outside the scanned dirs is
|
|
144
|
+
invisible, so soundness requires every consumer of a prop to be in scope.
|
|
145
|
+
|
|
146
|
+
## Architecture & status
|
|
147
|
+
|
|
148
|
+
The engine is split into an environment-free **Engine** (Vue-aware analysis +
|
|
149
|
+
transform) behind a stable IR, and a thin **Shell** (the Vite/Rollup plugin) that
|
|
150
|
+
owns file IO and module resolution — so the core can later be ported to Rust on
|
|
151
|
+
[vize](https://github.com/baseballyama/vize) (the Rust Vue toolchain). See
|
|
152
|
+
[`docs/ARCHITECTURE.md`](https://github.com/baseballyama/vue-shaker/blob/main/docs/ARCHITECTURE.md) and
|
|
153
|
+
[`docs/RUST-MIGRATION.md`](https://github.com/baseballyama/vue-shaker/blob/main/docs/RUST-MIGRATION.md).
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var e=require("@vue/compiler-sfc"),t=require("@babel/parser"),n=require("magic-string"),o=require("postcss"),r=require("postcss-selector-parser");const s=1,i=2,c=3,a=6,u=7,f=1,l=e=>e.type===s&&e.tagType===f,p=["typescript"];function d(n,o){const{descriptor:r}=e.parse(n,{filename:o}),s=r;let i;if(s.scriptSetup){const e=s.scriptSetup,n=e.attrs?.lang;i={content:e.content,base:e.loc.start.offset,endOffset:e.loc.end.offset,ast:(c=e.content,t.parse(c,{sourceType:"module",plugins:[...p]}).program),lang:"string"==typeof n?n:void 0}}var c;const a=(s.styles??[]).map(e=>({content:e.content,base:e.loc.start.offset,scoped:!0===e.scoped}));return{id:o,code:n,template:s.template?.ast??void 0,scriptSetup:i,hasPlainScript:null!=s.script,styles:a}}function m(e,t,n){if(!n)return d(t,e);const o=n.get(e);if(o&&o.code===t)return o.sfc;const r=d(t,e);return n.set(e,{code:t,sfc:r}),r}function y(e){if(null!=e&&""!==e.trim())try{return t.parseExpression(e,{plugins:[...p]})}catch{return}}function v(e){if(e)return"string"==typeof e.content?e.content:void 0}function w(e,t){if(!e)return;if(!1!==t(e))for(const n of e.children??[])w(n,t)}function g(e,t){if(!e)return;const n=e.children;if(n){t(n,e);for(const e of n)g(e,t)}}function h(e,t,n=null){if(e&&"object"==typeof e&&"string"==typeof e.type){t(e,n);for(const n of Object.values(e))if(Array.isArray(n))for(const o of n)h(o,t,e);else n&&"object"==typeof n&&"string"==typeof n.type&&h(n,t,e)}}const k={known:!1};function S(e,t){if(!e)return k;switch(e.type){case"Literal":case"StringLiteral":case"NumericLiteral":case"BooleanLiteral":return{known:!0,value:e.value};case"NullLiteral":return{known:!0,value:null};case"Identifier":{const n=e.name??"";return"undefined"===n?{known:!0,value:void 0}:t.has(n)?{known:!0,value:t.get(n)}:k}case"UnaryExpression":{const n=S(e.argument,t);if(!n.known)return k;const o=n.value;switch(e.operator){case"!":return{known:!0,value:!o};case"-":return{known:!0,value:-o};case"+":return{known:!0,value:+o};case"typeof":return{known:!0,value:typeof o};case"void":return{known:!0,value:void 0};default:return k}}case"LogicalExpression":{const n=S(e.left,t);if(!n.known)return k;switch(e.operator){case"&&":return n.value?S(e.right,t):n;case"||":return n.value?n:S(e.right,t);case"??":return null===n.value||void 0===n.value?S(e.right,t):n;default:return k}}case"BinaryExpression":{const n=S(e.left,t),o=S(e.right,t);if(!n.known||!o.known)return k;const r=n.value,s=o.value;switch(e.operator){case"===":return{known:!0,value:r===s};case"!==":return{known:!0,value:r!==s};case"==":return{known:!0,value:r==s};case"!=":return{known:!0,value:r!=s};case"<":return{known:!0,value:r<s};case">":return{known:!0,value:r>s};case"<=":return{known:!0,value:r<=s};case">=":return{known:!0,value:r>=s};case"+":return{known:!0,value:r+s};case"-":return{known:!0,value:r-s};case"*":return{known:!0,value:r*s};case"/":return{known:!0,value:r/s};case"%":return{known:!0,value:r%s};default:return k}}default:return k}}function b(e,t,n){const o=S(e,t);if(o.known)return o;const r=x(e,t,n);return"unknown"===r?k:{known:!0,value:r}}function x(e,t,n){if(!e)return"unknown";switch(e.type){case"UnaryExpression":return"!"===e.operator?P(x(e.argument,t,n)):"unknown";case"LogicalExpression":{const s=x(e.left,t,n),i=()=>x(e.right,t,n);return"&&"===e.operator?!1!==s&&(o=s,r=i(),!1!==o&&!1!==r&&(!0===o&&!0===r||"unknown")):"||"===e.operator?!0===s||function(e,t){return!0===e||!0===t||(!1!==e||!1!==t)&&"unknown"}(s,i()):"unknown"}case"BinaryExpression":{const o=e.operator;if("==="===o||"=="===o||"!=="===o||"!="===o){const r="=="===o||"!="===o,s=function(e,t,n,o,r){const s=I(e,o),i=S(t,n);if(s&&i.known)return E(s,i.value,r);const c=I(t,o),a=S(e,n);return c&&a.known?E(c,a.value,r):a.known&&i.known?r?a.value==i.value:a.value===i.value:"unknown"}(e.left,e.right,t,n,r);return"!=="===o||"!="===o?P(s):s}return"unknown"}default:return"unknown"}var o,r}function I(e,t){return"Identifier"===e?.type&&e.name&&t.has(e.name)?t.get(e.name):null}function E(e,t,n){const o=n?e=>e==t:e=>e===t;return!!e.some(o)&&(!!e.every(o)||"unknown")}function P(e){return!0!==e&&(!1===e||"unknown")}function M(e){if(e.type===s)for(const t of e.props??[])if(t.type===u){if("if"===t.name)return{dir:t,kind:"if"};if("else-if"===t.name)return{dir:t,kind:"else-if"};if("else"===t.name)return{dir:t,kind:"else"}}}const L=e=>e.type===i&&"string"==typeof e.content&&""===e.content.trim()||e.type===c;function D(e){const t=[];for(let n=0;n<e.length;n++){const o=e[n],r=M(o);if(!r||"if"!==r.kind)continue;const s=[R(o,r.dir,"if")];let i=n+1;for(;i<e.length;i++){const t=e[i];if(L(t))continue;const n=M(t);if(!n||"if"===n.kind)break;if(s.push(R(t,n.dir,n.kind)),"else"===n.kind){i++;break}}t.push(s),n=i-1}return t}function R(e,t,n){return{el:e,dir:t,kind:n,testAst:"else"===n?void 0:y(v(t.exp))}}function j(e,t,n){const o=e.map(e=>"else"===e.kind?{known:!0,value:!0}:b(e.testAst,t,n)),r=e=>e.known&&Boolean(e.value),s=e=>e.known&&!e.value,i=e=>[e.el.loc.start.offset,e.el.loc.end.offset],c=e=>[e.dir.loc.start.offset,e.dir.loc.end.offset];let a=!0;for(let t=0;t<e.length;t++){if(r(o[t])&&a){const n=[];for(let o=0;o<e.length;o++)o!==t&&n.push(i(e[o]));return{removed:n,dirRemovals:[c(e[t])]}}s(o[t])||(a=!1)}const u=o.findIndex(e=>!s(e));if(-1===u)return{removed:e.map(i),dirRemovals:[]};const f=[];for(let t=0;t<u;t++)f.push(i(e[t]));for(let t=u+1;t<e.length;t++)s(o[t])&&f.push(i(e[t]));if(0===u)return{removed:f,dirRemovals:[]};const l=e[u];if("else"===l.kind)return{removed:f,dirRemovals:[c(l)]};const p=l.dir.loc.start.offset;return{removed:f,dirRemovals:[],promotion:{from:p,to:p+9,text:"v-if"}}}function z(e,t){const n=e.loc;return!!n&&function(e,t){return t.some(([t,n])=>e[0]>=t&&e[1]<=n)}([n.start.offset,n.end.offset],t)}function O(e,t,n){if(!e||0===t.size&&0===n.size)return[];const o=[];return g(e,e=>{for(const r of D(e)){if(z(r[0].el,o))continue;const e=j(r,t,n);for(const t of e.removed)o.push(t)}}),o}const A=e=>e.endsWith(".vue"),C='escapes as value (e.g. <component :is="X">)',F="rendered through a barrel/named import (call sites unobservable)";function N(e){return e.sfc.scriptSetup?.base??0}async function q(e,t,n){return B(await $(e,t,n))}function B(e,t){const n=function(e,t){const n=new Map;for(const t of e.edges){const e=n.get(t.from);e?e.push(t):n.set(t.from,[t])}const o=new Map;for(const r of e.files)o.set(r.id,G(r,n.get(r.id)??[],t));return o}(e,t),o=new Set;for(const e of n.values())for(const t of e.escapedComponents)o.add(t);for(const e of o){const t=n.get(e);t&&!t.bailReasons.includes(C)&&t.bailReasons.push(C)}const r=new Set;for(const e of n.values())for(const t of e.barrelChildIds)r.add(t);for(const e of r){const t=n.get(e);t&&!t.bailReasons.includes(F)&&t.bailReasons.push(F)}let s=U(n,T(n,new Map));for(let e=0;e<10;e++){const e=U(n,T(n,X(n,s)));if(V(s,e)){s=e;break}s=e}return{models:n,plans:s}}async function $(e,t,n,o){const r=Array.isArray(e)?[...e]:[e],s=[],i=[],c=[...r],a=new Set(c);for(;c.length>0;){const e=c.shift();let r,u;try{r=await n(e)}catch{continue}s.push({id:e,code:r});try{u=m(e,r,o)}catch{continue}if(!u.scriptSetup)continue;const f=new Map,l=[];for(const o of ue(u.scriptSetup.ast)){if("default"===o.imported&&A(o.value)){const n=await t(o.value,e);n&&(i.push({from:e,local:o.local,to:n,kind:"default-vue"}),l.push(n));continue}const r=await de(o.value,o.imported,e,t,n);r&&(i.push({from:e,local:o.local,to:r,kind:"barrel"}),f.set(o.local,r))}const p=Z(u.template,f);for(const e of[...l,...p])a.has(e)||(a.add(e),c.push(e))}return{files:s,edges:i,entries:r}}function T(e,t){const n=new Map,o=e=>{let t=n.get(e);return t||(t={sites:[]},n.set(e,t)),t};for(const n of e.values()){const e=t.get(n.id)??[];N(n);for(const t of n.childCalls)e.length>0&&z(t.node,e)||o(t.childId).sites.push(ee(t.node))}return n}function U(e,t){const n=new Map;for(const o of e.values())n.set(o.id,ie(o,t.get(o.id)));return n}function V(e,t){if(e.size!==t.size)return!1;for(const[n,o]of e){const e=t.get(n);if(!e)return!1;if(o.bail!==e.bail)return!1;if(!J(o.constFold,e.constFold))return!1;if(!W(o.narrow,e.narrow))return!1}return!0}function J(e,t){if(e.size!==t.size)return!1;for(const[n,o]of e)if(!t.has(n)||!Object.is(t.get(n),o))return!1;return!0}function W(e,t){if(e.size!==t.size)return!1;for(const[n,o]of e){const e=t.get(n);if(!e||o.length!==e.length)return!1;for(let t=0;t<o.length;t++)if(!Object.is(o[t],e[t]))return!1}return!0}function X(e,t){const n=new Map;for(const o of e.values()){const e=t.get(o.id);if(e.bail)continue;const r=O(o.sfc.template,e.constFold,e.narrow);r.length>0&&n.set(o.id,r)}return n}function G(e,t,n){const{id:o,code:r}=e,i=m(o,r,n),c=new Map,a=new Map;for(const e of t)"default-vue"===e.kind?c.set(e.local,e.to):a.set(e.local,e.to);const f=[];if(i.scriptSetup)for(const e of function(e){const t=new Set;return h(e,e=>{"CallExpression"===e.type&&"Identifier"===e.callee?.type&&e.callee.name&&t.add(e.callee.name)}),t}(i.scriptSetup.ast))"defineExpose"===e&&f.push("defineExpose() (public instance surface)");let p,d,g,k=null,S=!1,b=!1;const x=new Set;if(i.scriptSetup){for(const e of ue(i.scriptSetup.ast))x.add(e.local);const e=function(e){const t=e.body??[];for(const e of t)if("VariableDeclaration"===e.type)for(const t of e.declarations??[]){const n=t.init,o=t.id;if("ObjectPattern"!==o?.type||"CallExpression"!==n?.type)continue;const r="Identifier"===n.callee?.type?n.callee.name:void 0;if("defineProps"===r)return{declaration:e,pattern:o,definePropsCall:n,withDefaults:new Map,sharesStatement:(e.declarations?.length??1)>1};if("withDefaults"===r){const t=n.arguments?.[0];if("CallExpression"!==t?.type||"Identifier"!==t.callee?.type)continue;if("defineProps"!==t.callee.name)continue;return{declaration:e,pattern:o,definePropsCall:t,withDefaults:ye(n.arguments?.[1]),sharesStatement:(e.declarations?.length??1)>1}}}return null}(i.scriptSetup.ast);if(e){d=e.declaration,p=e.pattern,g=e.definePropsCall,b=e.sharesStatement,e.sharesStatement&&f.push("defineProps() shares a multi-declarator statement"),k=[];for(const t of e.pattern.properties??[]){if("RestElement"===t.type){S=!0;continue}if("ObjectProperty"!==t.type)continue;const n=t.key;if("Identifier"!==n?.type||!n.name)continue;const o=t.value,r="AssignmentPattern"===o?.type?o.right:void 0;k.push({name:n.name,property:t,defaultExpr:r??e.withDefaults.get(n.name)})}}}const I=function(e,t){const n=[];return w(e,e=>{if(l(e)&&e.tag){const o=t.get(e.tag);o&&n.push({childId:o,node:e})}}),n}(i.template,c),E=Z(i.template,a),P=function(e){const t=new Set;w(e.template,e=>{if(e.type===s)for(const n of e.props??[])n.type===u&&("for"===n.name?H(v(n.exp),t):"slot"===n.name&&K(v(n.exp),t))});const n=e.scriptSetup;n&&h(n.ast,e=>{if("FunctionDeclaration"===e.type||"FunctionExpression"===e.type||"ArrowFunctionExpression"===e.type)for(const n of e.params??[])Q(n,t)});return t}(i),M=function(e,t,n){const o=new Set,r=e=>{if(!e)return;const n=t.get(e);n&&o.add(n)};w(e.template,e=>{if(e.type===s)for(const t of e.props??[])if(t.type===u&&"bind"===t.name&&"is"===_(t.arg)){const e=y(v(t.exp));"Identifier"===e?.type&&r(e.name)}});const i=e.scriptSetup;i&&h(i.ast,(e,o)=>{"Identifier"===e.type&&e.name&&t.has(e.name)&&n.has(e.name)&&function(e,t){return!!t&&(!("MemberExpression"===t.type&&t.property===e&&!t.computed)&&(!!("ObjectProperty"!==t.type&&"Property"!==t.type||t.key!==e||t.computed||!0===t.shorthand)&&!Y(t)))}(e,o)&&!Y(o)&&r(e.name)});return o}(i,c,x);return{id:o,code:r,sfc:i,imports:c,props:k,propsPattern:p,propsDeclaration:d,definePropsCall:g,hasRestProp:S,sharesStatement:b,childCalls:I,shadowedNames:P,escapedComponents:M,barrelChildIds:E,bailReasons:f}}function H(e,t){if(!e)return;const n=/^(.*?)\s+(?:in|of)\s+/s.exec(e),o=(n?n[1]:e).trim();o&&K(o,t)}function K(e,t){if(!e)return;const n=y(`(${e}) => 0`);if("ArrowFunctionExpression"!==n?.type){const n=y(e);return void(n&&Q(n,t))}for(const e of n.params??[])Q(e,t)}function Q(e,t){if(e)switch(e.type){case"Identifier":return void(e.name&&t.add(e.name));case"ObjectExpression":case"ObjectPattern":for(const n of e.properties??[])"RestElement"===n.type?Q(n.argument,t):"ObjectProperty"!==n.type&&"Property"!==n.type||Q(n.value??n.key,t);return;case"ArrayExpression":case"ArrayPattern":for(const n of e.elements??[])Q(n,t);return;case"AssignmentPattern":return void Q(e.left,t);case"RestElement":return void Q(e.argument,t);default:return}}function Y(e){return null!=e&&("ImportSpecifier"===e.type||"ImportDefaultSpecifier"===e.type||"ImportNamespaceSpecifier"===e.type||"ExportSpecifier"===e.type)}function Z(e,t){const n=new Set;return 0===t.size||w(e,e=>{if(l(e)&&e.tag){const o=t.get(e.tag);o&&n.add(o)}}),n}function _(e){return v(e)}function ee(e,t){const n=e.props??[];let o=-1;for(let e=0;e<n.length;e++){const t=n[e];t.type===u&&"bind"===t.name&&te(t)&&(o=e)}const r=new Map;for(let e=0;e<n.length;e++){const t=n[e],s=e>o;if(t.type===a){if(!t.name)continue;const e=oe(t);r.set(t.name,{value:e,dynamic:!1,afterLastSpread:s});continue}if(t.type===u){if("bind"===t.name){if(te(t))continue;const e=v(t.arg);if(!e)continue;const n=re(v(t.exp));r.set(e,n.known?{value:n.value,dynamic:!1,afterLastSpread:s}:ne(s));continue}if("model"===t.name){const e=v(t.arg)??"modelValue";r.set(e,ne(s));continue}}}return{hadSpread:o>=0,explicit:r}}function te(e){return null==e.arg||null==v(e.arg)}function ne(e){return{value:void 0,dynamic:!0,afterLastSpread:e}}function oe(e){const t=e.value;return!t||("string"==typeof t.content?t.content:"")}function re(e){const t=y(e);return t?S(t,new Map):{known:!1}}function se(e,t){return e.shadowedNames.has(t)}function ie(e,t){const n={id:e.id,bail:!1,reasons:[],constFold:new Map,narrow:new Map,valueSets:new Map};if(e.bailReasons.length>0)return n.bail=!0,n.reasons.push(...e.bailReasons),n;if(!e.props||0===e.props.length)return n;const o=t?.sites??[];if(0===o.length)return n;for(const t of e.props){if(se(e,t.name))continue;const r=ce(t,o);n.valueSets.set(t.name,r),r.top||r.dynamic||(1!==r.values.length?r.values.length>=2&&n.narrow.set(t.name,r.values):n.constFold.set(t.name,r.values[0]))}return n}function ce(e,t){const n=[];let o=!1,r=!1;const s=e=>{n.some(t=>Object.is(t,e))||n.push(e)};for(const n of t){const t=n.explicit.get(e.name);if(t?.afterLastSpread){t.dynamic?o=!0:s(t.value);continue}if(n.hadSpread){r=!0;continue}const i=ae(e.defaultExpr);i.known?s(i.value):o=!0}return{values:n,dynamic:o,top:r}}function ae(e){return e?S(e,new Map):{known:!0,value:void 0}}function*ue(e){const t=e.body??[];for(const e of t){if("ImportDeclaration"!==e.type)continue;const t=e.source?.value;if("string"==typeof t)for(const n of e.specifiers??[]){const e=n.local?.name;e&&("ImportDefaultSpecifier"===n.type?yield{value:t,local:e,imported:"default"}:"ImportNamespaceSpecifier"===n.type?yield{value:t,local:e,imported:"*"}:"ImportSpecifier"===n.type&&(yield{value:t,local:e,imported:fe(n)??e}))}}}function fe(e){const t=e.imported;return"Identifier"===t?.type&&t.name?t.name:"StringLiteral"===t?.type&&"string"==typeof t.value?t.value:void 0}function le(e){return"Identifier"===e?.type&&e.name?e.name:"StringLiteral"===e?.type&&"string"==typeof e.value?e.value:void 0}const pe=8;async function de(e,t,n,o,r,s=0){if(s>pe)return null;const i=await o(e,n);if(!i)return null;if(A(e)||A(i))return"default"===t||"*"===t?i:null;let c;try{c=await r(i)}catch{return null}const a=function(e){try{const t=d(`<script setup>\n${e}\n<\/script>`,"barrel.vue");return t.scriptSetup?.ast.body??null}catch{return null}}(c);if(!a)return null;for(const e of a)if("ExportNamedDeclaration"===e.type&&e.source?.value){for(const n of e.specifiers??[])if(le(n.exported)===t)return de(String(e.source.value),le(n.local)??"default",i,o,r,s+1)}else if("ExportNamedDeclaration"!==e.type||e.source){if("ExportAllDeclaration"===e.type&&e.source?.value){const n=await de(String(e.source.value),t,i,o,r,s+1);if(n)return n}}else for(const n of e.specifiers??[]){if(le(n.exported)!==t)continue;const e=le(n.local);if(!e)continue;const c=me(a,e);return c?de(c.value,c.imported,i,o,r,s+1):null}return null}function me(e,t){for(const n of e){if("ImportDeclaration"!==n.type)continue;const e=n.source?.value;if("string"==typeof e)for(const o of n.specifiers??[])if(o.local?.name===t){if("ImportDefaultSpecifier"===o.type)return{value:e,imported:"default"};if("ImportNamespaceSpecifier"===o.type)return{value:e,imported:"*"};if("ImportSpecifier"===o.type)return{value:e,imported:fe(o)??t}}}return null}function ye(e){const t=new Map;if("ObjectExpression"!==e?.type)return t;for(const n of e.properties??[]){if("ObjectProperty"!==n.type)continue;const e=n.key,o="Identifier"===e?.type?e.name:void 0;o&&n.value&&t.set(o,n.value)}return t}const ve=Symbol("unbounded-class-source");function we(e,t,n){if(!e)return ve;if("ObjectExpression"===e.type){const t=new Set;for(const n of e.properties??[]){if("ObjectProperty"!==n.type)return ve;const e=n.key,o="Identifier"===e?.type?e.name:"StringLiteral"===e?.type?e.value:void 0;if(null==o)return ve;for(const e of o.split(/\s+/))e&&t.add(e)}return t}if("ArrayExpression"===e.type){const o=new Set;for(const r of e.elements??[]){if(!r)continue;const e=we(r,t,n);if(e===ve)return ve;for(const t of e)o.add(t)}return o}const o=ge(e,t,n);if(o===ve)return ve;const r=new Set;for(const e of o)for(const t of e.split(/\s+/))t&&r.add(t);return r}function ge(e,t,n){if(!e)return ve;switch(e.type){case"StringLiteral":case"NumericLiteral":case"BooleanLiteral":return new Set([String(e.value)]);case"Identifier":{const o=e.name??"";if(n.has(o)){const e=new Set;for(const t of n.get(o))e.add(String(t));return e}const r=S(e,t);return r.known?new Set([String(r.value)]):ve}case"TemplateLiteral":{const o=e.quasis??[],r=e.expressions??[];let s=[he(o[0])];for(let e=0;e<r.length;e++){const i=ge(r[e],t,n);if(i===ve)return ve;const c=he(o[e+1]),a=[];for(const e of s)for(const t of i)if(a.push(e+t+c),a.length>64)return ve;s=a}return new Set(s)}case"BinaryExpression":{if("+"!==e.operator)break;const o=ge(e.left,t,n),r=ge(e.right,t,n);if(o===ve||r===ve)return ve;const s=new Set;for(const e of o)for(const t of r)if(s.add(e+t),s.size>64)return ve;return s}case"ConditionalExpression":{const o=S(e.test,t);if(o.known)return ge(o.value?e.consequent:e.alternate,t,n);const r=ge(e.consequent,t,n),s=ge(e.alternate,t,n);return r===ve||s===ve?ve:new Set([...r,...s])}}const o=S(e,t);return o.known?new Set([String(o.value)]):ve}function he(e){const t=e?.value;return t?.cooked??t?.raw??""}function ke(e,t,n){const r=function(e,t){const n=new Set;let o=!1;const r=t.constFold,i=t.narrow;return w(e.sfc.template,e=>{if(e.type===s)for(const t of e.props??[]){if(t.type===a){if("class"===t.name){const e=t.value,o="string"==typeof e?.content?e.content:"";for(const e of o.split(/\s+/))e&&n.add(e)}continue}if(t.type!==u||"bind"!==t.name)continue;const e=v(t.arg);if(null==t.arg||null==e){o=!0;continue}if("class"!==e)continue;const s=we(y(v(t.exp)),r,i);if(s===ve)o=!0;else for(const e of s)n.add(e)}}),{classes:n,unbounded:o}}(e,t);if(r.unbounded)return 0;let i=0;for(const t of e.sfc.styles){if(!t.scoped)continue;let e;try{e=o.parse(t.content)}catch{continue}let s=0;if(e.each(e=>{"rule"===e.type&&Se(e,r.classes)&&(e.remove(),s+=1)}),s>0){const o=t.base+t.content.length;n.overwrite(t.base,o,e.toString()),i+=s}}return i}function Se(e,t){let n=!1;try{r(e=>{let o=!1,r=!0,s=!1;e.each(e=>{s=!0;const n=[];e.walkPseudos(e=>{const t=e.value;(t.includes("deep")||t.includes("global")||t.includes("slotted"))&&(o=!0)}),e.walkClasses(e=>{n.push(e.value)});n.some(e=>!t.has(e))||(r=!1)}),n=s&&r&&!o}).processSync(e.selector)}catch{return!1}return n}function be(e,t){const o=new Map,r=new Map;for(const s of e.values()){const e=new n(s.code);o.set(s.id,e);const i=t.get(s.id);r.set(s.id,i.bail?new Set:xe(s,i.constFold,i.narrow,i,e))}for(const t of e.values())Le(t,r,o.get(t.id));const s={};for(const t of e.values())s[t.id]=o.get(t.id).toString();return s}function xe(e,t,n,o,r){if(0===t.size&&0===n.size)return new Set;!function(e,t,n,o,r){const s=e.code;g(e.sfc.template,e=>{for(const i of D(e)){if(z(i[0].el,r))continue;const e=j(i,t,n);for(const[t,n]of e.removed)Ie(s,t,n,o),r.push([t,n]);for(const[t,n]of e.dirRemovals)Ie(s,t,n,o);if(e.promotion){const{from:t,to:n,text:r}=e.promotion;o.overwrite(t,n,r)}}})}(e,t,n,r,[]);const s=function(e,t,n){const o=new Set,r=e.sfc.scriptSetup;if(!(e.props&&0!==t.size&&r&&e.propsDeclaration&&e.propsPattern))return o;const s=r.base,i=e.props.filter(e=>t.has(e.name));if(0===i.length)return o;for(const e of i)o.add(e.name);const c=i.map(e=>{return`const ${e.name} = ${n=t.get(e.name),void 0===n?"undefined":JSON.stringify(n)};`;var n}).join("\n"),a=e.props.filter(e=>!t.has(e.name)),u=s+(e.propsDeclaration.start??0);let f=s+(e.propsDeclaration.end??0);";"===e.code[f]&&(f+=1);if(0===a.length&&!e.hasRestProp)return n.overwrite(u,f,c),o;const l=e.propsPattern?.properties??[];for(const t of i)Ee(l,t.property,s,n,e.code),Pe(e.definePropsCall,t.name,s,n,e.code);return n.appendLeft(f,`\n${c}`),o}(e,t,r);return ke(e,{...o,constFold:t,narrow:n},r),s}function Ie(e,t,n,o){let r=t;for(;r>0&&(" "===e[r-1]||"\t"===e[r-1]);)r-=1;o.remove(r,n)}function Ee(e,t,n,o,r){const s=e.indexOf(t);Me(t,e[s-1],n,o,r)}function Pe(e,t,n,o,r){const s=e?.typeParameters?.params?.[0],i=s?.members??[],c=i.findIndex(e=>"Identifier"===e.key?.type&&e.key.name===t);-1!==c&&Me(i[c],i[c-1],n,o,r)}function Me(e,t,n,o,r){const s=n+(e.start??0),i=n+(e.end??0);let c=i;for(;c<r.length&&(" "===r[c]||"\t"===r[c]);)c+=1;if(","===r[c]){let e=c+1;for(;e<r.length&&(" "===r[e]||"\t"===r[e]||"\n"===r[e]);)e+=1;return void o.remove(s,e)}t?o.remove(n+(t.end??0),i):o.remove(s,i)}function Le(e,t,n){const o=e.code;w(e.sfc.template,r=>{if(!l(r)||!r.tag)return;const s=e.imports.get(r.tag),i=s?t.get(s):void 0;if(i&&0!==i.size)for(const e of r.props??[])if(e.type===a)e.name&&i.has(e.name)&&Re(o,e,n);else if(e.type===u&&"bind"===e.name){const t=v(e.arg);t&&i.has(t)&&De(e)&&Re(o,e,n)}})}function De(e){const t=v(e.exp);if(null==t)return!1;const n=y(t);return null!=n&&S(n,new Map).known}function Re(e,t,n){const o=t.loc;let r=o.start.offset;for(;r>0&&(" "===e[r-1]||"\t"===e[r-1]||"\n"===e[r-1]);)r-=1;n.remove(r,o.end.offset)}exports.analyze=q,exports.analyzeInput=B,exports.buildAnalyzeInput=$,exports.transformAll=be,exports.vueShaker=async function(e,t,n){const{models:o,plans:r}=await q(e,t,n);return be(o,r)};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{parse as e}from"@vue/compiler-sfc";import{parseExpression as t,parse as n}from"@babel/parser";import o from"magic-string";import{parse as r}from"postcss";import s from"postcss-selector-parser";const i=1,c=2,a=3,u=6,f=7,l=1,p=e=>e.type===i&&e.tagType===l,d=["typescript"];function m(t,o){const{descriptor:r}=e(t,{filename:o}),s=r;let i;if(s.scriptSetup){const e=s.scriptSetup,t=e.attrs?.lang;i={content:e.content,base:e.loc.start.offset,endOffset:e.loc.end.offset,ast:(c=e.content,n(c,{sourceType:"module",plugins:[...d]}).program),lang:"string"==typeof t?t:void 0}}var c;const a=(s.styles??[]).map(e=>({content:e.content,base:e.loc.start.offset,scoped:!0===e.scoped}));return{id:o,code:t,template:s.template?.ast??void 0,scriptSetup:i,hasPlainScript:null!=s.script,styles:a}}function y(e,t,n){if(!n)return m(t,e);const o=n.get(e);if(o&&o.code===t)return o.sfc;const r=m(t,e);return n.set(e,{code:t,sfc:r}),r}function v(e){if(null!=e&&""!==e.trim())try{return t(e,{plugins:[...d]})}catch{return}}function w(e){if(e)return"string"==typeof e.content?e.content:void 0}function g(e,t){if(!e)return;if(!1!==t(e))for(const n of e.children??[])g(n,t)}function h(e,t){if(!e)return;const n=e.children;if(n){t(n,e);for(const e of n)h(e,t)}}function k(e,t,n=null){if(e&&"object"==typeof e&&"string"==typeof e.type){t(e,n);for(const n of Object.values(e))if(Array.isArray(n))for(const o of n)k(o,t,e);else n&&"object"==typeof n&&"string"==typeof n.type&&k(n,t,e)}}const S={known:!1};function b(e,t){if(!e)return S;switch(e.type){case"Literal":case"StringLiteral":case"NumericLiteral":case"BooleanLiteral":return{known:!0,value:e.value};case"NullLiteral":return{known:!0,value:null};case"Identifier":{const n=e.name??"";return"undefined"===n?{known:!0,value:void 0}:t.has(n)?{known:!0,value:t.get(n)}:S}case"UnaryExpression":{const n=b(e.argument,t);if(!n.known)return S;const o=n.value;switch(e.operator){case"!":return{known:!0,value:!o};case"-":return{known:!0,value:-o};case"+":return{known:!0,value:+o};case"typeof":return{known:!0,value:typeof o};case"void":return{known:!0,value:void 0};default:return S}}case"LogicalExpression":{const n=b(e.left,t);if(!n.known)return S;switch(e.operator){case"&&":return n.value?b(e.right,t):n;case"||":return n.value?n:b(e.right,t);case"??":return null===n.value||void 0===n.value?b(e.right,t):n;default:return S}}case"BinaryExpression":{const n=b(e.left,t),o=b(e.right,t);if(!n.known||!o.known)return S;const r=n.value,s=o.value;switch(e.operator){case"===":return{known:!0,value:r===s};case"!==":return{known:!0,value:r!==s};case"==":return{known:!0,value:r==s};case"!=":return{known:!0,value:r!=s};case"<":return{known:!0,value:r<s};case">":return{known:!0,value:r>s};case"<=":return{known:!0,value:r<=s};case">=":return{known:!0,value:r>=s};case"+":return{known:!0,value:r+s};case"-":return{known:!0,value:r-s};case"*":return{known:!0,value:r*s};case"/":return{known:!0,value:r/s};case"%":return{known:!0,value:r%s};default:return S}}default:return S}}function x(e,t,n){const o=b(e,t);if(o.known)return o;const r=I(e,t,n);return"unknown"===r?S:{known:!0,value:r}}function I(e,t,n){if(!e)return"unknown";switch(e.type){case"UnaryExpression":return"!"===e.operator?M(I(e.argument,t,n)):"unknown";case"LogicalExpression":{const s=I(e.left,t,n),i=()=>I(e.right,t,n);return"&&"===e.operator?!1!==s&&(o=s,r=i(),!1!==o&&!1!==r&&(!0===o&&!0===r||"unknown")):"||"===e.operator?!0===s||function(e,t){return!0===e||!0===t||(!1!==e||!1!==t)&&"unknown"}(s,i()):"unknown"}case"BinaryExpression":{const o=e.operator;if("==="===o||"=="===o||"!=="===o||"!="===o){const r="=="===o||"!="===o,s=function(e,t,n,o,r){const s=E(e,o),i=b(t,n);if(s&&i.known)return P(s,i.value,r);const c=E(t,o),a=b(e,n);return c&&a.known?P(c,a.value,r):a.known&&i.known?r?a.value==i.value:a.value===i.value:"unknown"}(e.left,e.right,t,n,r);return"!=="===o||"!="===o?M(s):s}return"unknown"}default:return"unknown"}var o,r}function E(e,t){return"Identifier"===e?.type&&e.name&&t.has(e.name)?t.get(e.name):null}function P(e,t,n){const o=n?e=>e==t:e=>e===t;return!!e.some(o)&&(!!e.every(o)||"unknown")}function M(e){return!0!==e&&(!1===e||"unknown")}function L(e){if(e.type===i)for(const t of e.props??[])if(t.type===f){if("if"===t.name)return{dir:t,kind:"if"};if("else-if"===t.name)return{dir:t,kind:"else-if"};if("else"===t.name)return{dir:t,kind:"else"}}}const D=e=>e.type===c&&"string"==typeof e.content&&""===e.content.trim()||e.type===a;function R(e){const t=[];for(let n=0;n<e.length;n++){const o=e[n],r=L(o);if(!r||"if"!==r.kind)continue;const s=[j(o,r.dir,"if")];let i=n+1;for(;i<e.length;i++){const t=e[i];if(D(t))continue;const n=L(t);if(!n||"if"===n.kind)break;if(s.push(j(t,n.dir,n.kind)),"else"===n.kind){i++;break}}t.push(s),n=i-1}return t}function j(e,t,n){return{el:e,dir:t,kind:n,testAst:"else"===n?void 0:v(w(t.exp))}}function O(e,t,n){const o=e.map(e=>"else"===e.kind?{known:!0,value:!0}:x(e.testAst,t,n)),r=e=>e.known&&Boolean(e.value),s=e=>e.known&&!e.value,i=e=>[e.el.loc.start.offset,e.el.loc.end.offset],c=e=>[e.dir.loc.start.offset,e.dir.loc.end.offset];let a=!0;for(let t=0;t<e.length;t++){if(r(o[t])&&a){const n=[];for(let o=0;o<e.length;o++)o!==t&&n.push(i(e[o]));return{removed:n,dirRemovals:[c(e[t])]}}s(o[t])||(a=!1)}const u=o.findIndex(e=>!s(e));if(-1===u)return{removed:e.map(i),dirRemovals:[]};const f=[];for(let t=0;t<u;t++)f.push(i(e[t]));for(let t=u+1;t<e.length;t++)s(o[t])&&f.push(i(e[t]));if(0===u)return{removed:f,dirRemovals:[]};const l=e[u];if("else"===l.kind)return{removed:f,dirRemovals:[c(l)]};const p=l.dir.loc.start.offset;return{removed:f,dirRemovals:[],promotion:{from:p,to:p+9,text:"v-if"}}}function C(e,t){const n=e.loc;return!!n&&function(e,t){return t.some(([t,n])=>e[0]>=t&&e[1]<=n)}([n.start.offset,n.end.offset],t)}function z(e,t,n){if(!e||0===t.size&&0===n.size)return[];const o=[];return h(e,e=>{for(const r of R(e)){if(C(r[0].el,o))continue;const e=O(r,t,n);for(const t of e.removed)o.push(t)}}),o}const A=e=>e.endsWith(".vue"),F='escapes as value (e.g. <component :is="X">)',N="rendered through a barrel/named import (call sites unobservable)";function B(e){return e.sfc.scriptSetup?.base??0}async function $(e,t,n){return q(await T(e,t,n))}function q(e,t){const n=function(e,t){const n=new Map;for(const t of e.edges){const e=n.get(t.from);e?e.push(t):n.set(t.from,[t])}const o=new Map;for(const r of e.files)o.set(r.id,H(r,n.get(r.id)??[],t));return o}(e,t),o=new Set;for(const e of n.values())for(const t of e.escapedComponents)o.add(t);for(const e of o){const t=n.get(e);t&&!t.bailReasons.includes(F)&&t.bailReasons.push(F)}const r=new Set;for(const e of n.values())for(const t of e.barrelChildIds)r.add(t);for(const e of r){const t=n.get(e);t&&!t.bailReasons.includes(N)&&t.bailReasons.push(N)}let s=V(n,U(n,new Map));for(let e=0;e<10;e++){const e=V(n,U(n,G(n,s)));if(J(s,e)){s=e;break}s=e}return{models:n,plans:s}}async function T(e,t,n,o){const r=Array.isArray(e)?[...e]:[e],s=[],i=[],c=[...r],a=new Set(c);for(;c.length>0;){const e=c.shift();let r,u;try{r=await n(e)}catch{continue}s.push({id:e,code:r});try{u=y(e,r,o)}catch{continue}if(!u.scriptSetup)continue;const f=new Map,l=[];for(const o of fe(u.scriptSetup.ast)){if("default"===o.imported&&A(o.value)){const n=await t(o.value,e);n&&(i.push({from:e,local:o.local,to:n,kind:"default-vue"}),l.push(n));continue}const r=await me(o.value,o.imported,e,t,n);r&&(i.push({from:e,local:o.local,to:r,kind:"barrel"}),f.set(o.local,r))}const p=_(u.template,f);for(const e of[...l,...p])a.has(e)||(a.add(e),c.push(e))}return{files:s,edges:i,entries:r}}function U(e,t){const n=new Map,o=e=>{let t=n.get(e);return t||(t={sites:[]},n.set(e,t)),t};for(const n of e.values()){const e=t.get(n.id)??[];B(n);for(const t of n.childCalls)e.length>0&&C(t.node,e)||o(t.childId).sites.push(te(t.node))}return n}function V(e,t){const n=new Map;for(const o of e.values())n.set(o.id,ce(o,t.get(o.id)));return n}function J(e,t){if(e.size!==t.size)return!1;for(const[n,o]of e){const e=t.get(n);if(!e)return!1;if(o.bail!==e.bail)return!1;if(!W(o.constFold,e.constFold))return!1;if(!X(o.narrow,e.narrow))return!1}return!0}function W(e,t){if(e.size!==t.size)return!1;for(const[n,o]of e)if(!t.has(n)||!Object.is(t.get(n),o))return!1;return!0}function X(e,t){if(e.size!==t.size)return!1;for(const[n,o]of e){const e=t.get(n);if(!e||o.length!==e.length)return!1;for(let t=0;t<o.length;t++)if(!Object.is(o[t],e[t]))return!1}return!0}function G(e,t){const n=new Map;for(const o of e.values()){const e=t.get(o.id);if(e.bail)continue;const r=z(o.sfc.template,e.constFold,e.narrow);r.length>0&&n.set(o.id,r)}return n}function H(e,t,n){const{id:o,code:r}=e,s=y(o,r,n),c=new Map,a=new Map;for(const e of t)"default-vue"===e.kind?c.set(e.local,e.to):a.set(e.local,e.to);const u=[];if(s.scriptSetup)for(const e of function(e){const t=new Set;return k(e,e=>{"CallExpression"===e.type&&"Identifier"===e.callee?.type&&e.callee.name&&t.add(e.callee.name)}),t}(s.scriptSetup.ast))"defineExpose"===e&&u.push("defineExpose() (public instance surface)");let l,d,m,h=null,S=!1,b=!1;const x=new Set;if(s.scriptSetup){for(const e of fe(s.scriptSetup.ast))x.add(e.local);const e=function(e){const t=e.body??[];for(const e of t)if("VariableDeclaration"===e.type)for(const t of e.declarations??[]){const n=t.init,o=t.id;if("ObjectPattern"!==o?.type||"CallExpression"!==n?.type)continue;const r="Identifier"===n.callee?.type?n.callee.name:void 0;if("defineProps"===r)return{declaration:e,pattern:o,definePropsCall:n,withDefaults:new Map,sharesStatement:(e.declarations?.length??1)>1};if("withDefaults"===r){const t=n.arguments?.[0];if("CallExpression"!==t?.type||"Identifier"!==t.callee?.type)continue;if("defineProps"!==t.callee.name)continue;return{declaration:e,pattern:o,definePropsCall:t,withDefaults:ve(n.arguments?.[1]),sharesStatement:(e.declarations?.length??1)>1}}}return null}(s.scriptSetup.ast);if(e){d=e.declaration,l=e.pattern,m=e.definePropsCall,b=e.sharesStatement,e.sharesStatement&&u.push("defineProps() shares a multi-declarator statement"),h=[];for(const t of e.pattern.properties??[]){if("RestElement"===t.type){S=!0;continue}if("ObjectProperty"!==t.type)continue;const n=t.key;if("Identifier"!==n?.type||!n.name)continue;const o=t.value,r="AssignmentPattern"===o?.type?o.right:void 0;h.push({name:n.name,property:t,defaultExpr:r??e.withDefaults.get(n.name)})}}}const I=function(e,t){const n=[];return g(e,e=>{if(p(e)&&e.tag){const o=t.get(e.tag);o&&n.push({childId:o,node:e})}}),n}(s.template,c),E=_(s.template,a),P=function(e){const t=new Set;g(e.template,e=>{if(e.type===i)for(const n of e.props??[])n.type===f&&("for"===n.name?K(w(n.exp),t):"slot"===n.name&&Q(w(n.exp),t))});const n=e.scriptSetup;n&&k(n.ast,e=>{if("FunctionDeclaration"===e.type||"FunctionExpression"===e.type||"ArrowFunctionExpression"===e.type)for(const n of e.params??[])Y(n,t)});return t}(s),M=function(e,t,n){const o=new Set,r=e=>{if(!e)return;const n=t.get(e);n&&o.add(n)};g(e.template,e=>{if(e.type===i)for(const t of e.props??[])if(t.type===f&&"bind"===t.name&&"is"===ee(t.arg)){const e=v(w(t.exp));"Identifier"===e?.type&&r(e.name)}});const s=e.scriptSetup;s&&k(s.ast,(e,o)=>{"Identifier"===e.type&&e.name&&t.has(e.name)&&n.has(e.name)&&function(e,t){return!!t&&(!("MemberExpression"===t.type&&t.property===e&&!t.computed)&&(!!("ObjectProperty"!==t.type&&"Property"!==t.type||t.key!==e||t.computed||!0===t.shorthand)&&!Z(t)))}(e,o)&&!Z(o)&&r(e.name)});return o}(s,c,x);return{id:o,code:r,sfc:s,imports:c,props:h,propsPattern:l,propsDeclaration:d,definePropsCall:m,hasRestProp:S,sharesStatement:b,childCalls:I,shadowedNames:P,escapedComponents:M,barrelChildIds:E,bailReasons:u}}function K(e,t){if(!e)return;const n=/^(.*?)\s+(?:in|of)\s+/s.exec(e),o=(n?n[1]:e).trim();o&&Q(o,t)}function Q(e,t){if(!e)return;const n=v(`(${e}) => 0`);if("ArrowFunctionExpression"!==n?.type){const n=v(e);return void(n&&Y(n,t))}for(const e of n.params??[])Y(e,t)}function Y(e,t){if(e)switch(e.type){case"Identifier":return void(e.name&&t.add(e.name));case"ObjectExpression":case"ObjectPattern":for(const n of e.properties??[])"RestElement"===n.type?Y(n.argument,t):"ObjectProperty"!==n.type&&"Property"!==n.type||Y(n.value??n.key,t);return;case"ArrayExpression":case"ArrayPattern":for(const n of e.elements??[])Y(n,t);return;case"AssignmentPattern":return void Y(e.left,t);case"RestElement":return void Y(e.argument,t);default:return}}function Z(e){return null!=e&&("ImportSpecifier"===e.type||"ImportDefaultSpecifier"===e.type||"ImportNamespaceSpecifier"===e.type||"ExportSpecifier"===e.type)}function _(e,t){const n=new Set;return 0===t.size||g(e,e=>{if(p(e)&&e.tag){const o=t.get(e.tag);o&&n.add(o)}}),n}function ee(e){return w(e)}function te(e,t){const n=e.props??[];let o=-1;for(let e=0;e<n.length;e++){const t=n[e];t.type===f&&"bind"===t.name&&ne(t)&&(o=e)}const r=new Map;for(let e=0;e<n.length;e++){const t=n[e],s=e>o;if(t.type===u){if(!t.name)continue;const e=re(t);r.set(t.name,{value:e,dynamic:!1,afterLastSpread:s});continue}if(t.type===f){if("bind"===t.name){if(ne(t))continue;const e=w(t.arg);if(!e)continue;const n=se(w(t.exp));r.set(e,n.known?{value:n.value,dynamic:!1,afterLastSpread:s}:oe(s));continue}if("model"===t.name){const e=w(t.arg)??"modelValue";r.set(e,oe(s));continue}}}return{hadSpread:o>=0,explicit:r}}function ne(e){return null==e.arg||null==w(e.arg)}function oe(e){return{value:void 0,dynamic:!0,afterLastSpread:e}}function re(e){const t=e.value;return!t||("string"==typeof t.content?t.content:"")}function se(e){const t=v(e);return t?b(t,new Map):{known:!1}}function ie(e,t){return e.shadowedNames.has(t)}function ce(e,t){const n={id:e.id,bail:!1,reasons:[],constFold:new Map,narrow:new Map,valueSets:new Map};if(e.bailReasons.length>0)return n.bail=!0,n.reasons.push(...e.bailReasons),n;if(!e.props||0===e.props.length)return n;const o=t?.sites??[];if(0===o.length)return n;for(const t of e.props){if(ie(e,t.name))continue;const r=ae(t,o);n.valueSets.set(t.name,r),r.top||r.dynamic||(1!==r.values.length?r.values.length>=2&&n.narrow.set(t.name,r.values):n.constFold.set(t.name,r.values[0]))}return n}function ae(e,t){const n=[];let o=!1,r=!1;const s=e=>{n.some(t=>Object.is(t,e))||n.push(e)};for(const n of t){const t=n.explicit.get(e.name);if(t?.afterLastSpread){t.dynamic?o=!0:s(t.value);continue}if(n.hadSpread){r=!0;continue}const i=ue(e.defaultExpr);i.known?s(i.value):o=!0}return{values:n,dynamic:o,top:r}}function ue(e){return e?b(e,new Map):{known:!0,value:void 0}}function*fe(e){const t=e.body??[];for(const e of t){if("ImportDeclaration"!==e.type)continue;const t=e.source?.value;if("string"==typeof t)for(const n of e.specifiers??[]){const e=n.local?.name;e&&("ImportDefaultSpecifier"===n.type?yield{value:t,local:e,imported:"default"}:"ImportNamespaceSpecifier"===n.type?yield{value:t,local:e,imported:"*"}:"ImportSpecifier"===n.type&&(yield{value:t,local:e,imported:le(n)??e}))}}}function le(e){const t=e.imported;return"Identifier"===t?.type&&t.name?t.name:"StringLiteral"===t?.type&&"string"==typeof t.value?t.value:void 0}function pe(e){return"Identifier"===e?.type&&e.name?e.name:"StringLiteral"===e?.type&&"string"==typeof e.value?e.value:void 0}const de=8;async function me(e,t,n,o,r,s=0){if(s>de)return null;const i=await o(e,n);if(!i)return null;if(A(e)||A(i))return"default"===t||"*"===t?i:null;let c;try{c=await r(i)}catch{return null}const a=function(e){try{const t=m(`<script setup>\n${e}\n<\/script>`,"barrel.vue");return t.scriptSetup?.ast.body??null}catch{return null}}(c);if(!a)return null;for(const e of a)if("ExportNamedDeclaration"===e.type&&e.source?.value){for(const n of e.specifiers??[])if(pe(n.exported)===t)return me(String(e.source.value),pe(n.local)??"default",i,o,r,s+1)}else if("ExportNamedDeclaration"!==e.type||e.source){if("ExportAllDeclaration"===e.type&&e.source?.value){const n=await me(String(e.source.value),t,i,o,r,s+1);if(n)return n}}else for(const n of e.specifiers??[]){if(pe(n.exported)!==t)continue;const e=pe(n.local);if(!e)continue;const c=ye(a,e);return c?me(c.value,c.imported,i,o,r,s+1):null}return null}function ye(e,t){for(const n of e){if("ImportDeclaration"!==n.type)continue;const e=n.source?.value;if("string"==typeof e)for(const o of n.specifiers??[])if(o.local?.name===t){if("ImportDefaultSpecifier"===o.type)return{value:e,imported:"default"};if("ImportNamespaceSpecifier"===o.type)return{value:e,imported:"*"};if("ImportSpecifier"===o.type)return{value:e,imported:le(o)??t}}}return null}function ve(e){const t=new Map;if("ObjectExpression"!==e?.type)return t;for(const n of e.properties??[]){if("ObjectProperty"!==n.type)continue;const e=n.key,o="Identifier"===e?.type?e.name:void 0;o&&n.value&&t.set(o,n.value)}return t}const we=Symbol("unbounded-class-source");function ge(e,t,n){if(!e)return we;if("ObjectExpression"===e.type){const t=new Set;for(const n of e.properties??[]){if("ObjectProperty"!==n.type)return we;const e=n.key,o="Identifier"===e?.type?e.name:"StringLiteral"===e?.type?e.value:void 0;if(null==o)return we;for(const e of o.split(/\s+/))e&&t.add(e)}return t}if("ArrayExpression"===e.type){const o=new Set;for(const r of e.elements??[]){if(!r)continue;const e=ge(r,t,n);if(e===we)return we;for(const t of e)o.add(t)}return o}const o=he(e,t,n);if(o===we)return we;const r=new Set;for(const e of o)for(const t of e.split(/\s+/))t&&r.add(t);return r}function he(e,t,n){if(!e)return we;switch(e.type){case"StringLiteral":case"NumericLiteral":case"BooleanLiteral":return new Set([String(e.value)]);case"Identifier":{const o=e.name??"";if(n.has(o)){const e=new Set;for(const t of n.get(o))e.add(String(t));return e}const r=b(e,t);return r.known?new Set([String(r.value)]):we}case"TemplateLiteral":{const o=e.quasis??[],r=e.expressions??[];let s=[ke(o[0])];for(let e=0;e<r.length;e++){const i=he(r[e],t,n);if(i===we)return we;const c=ke(o[e+1]),a=[];for(const e of s)for(const t of i)if(a.push(e+t+c),a.length>64)return we;s=a}return new Set(s)}case"BinaryExpression":{if("+"!==e.operator)break;const o=he(e.left,t,n),r=he(e.right,t,n);if(o===we||r===we)return we;const s=new Set;for(const e of o)for(const t of r)if(s.add(e+t),s.size>64)return we;return s}case"ConditionalExpression":{const o=b(e.test,t);if(o.known)return he(o.value?e.consequent:e.alternate,t,n);const r=he(e.consequent,t,n),s=he(e.alternate,t,n);return r===we||s===we?we:new Set([...r,...s])}}const o=b(e,t);return o.known?new Set([String(o.value)]):we}function ke(e){const t=e?.value;return t?.cooked??t?.raw??""}function Se(e,t,n){const o=function(e,t){const n=new Set;let o=!1;const r=t.constFold,s=t.narrow;return g(e.sfc.template,e=>{if(e.type===i)for(const t of e.props??[]){if(t.type===u){if("class"===t.name){const e=t.value,o="string"==typeof e?.content?e.content:"";for(const e of o.split(/\s+/))e&&n.add(e)}continue}if(t.type!==f||"bind"!==t.name)continue;const e=w(t.arg);if(null==t.arg||null==e){o=!0;continue}if("class"!==e)continue;const i=ge(v(w(t.exp)),r,s);if(i===we)o=!0;else for(const e of i)n.add(e)}}),{classes:n,unbounded:o}}(e,t);if(o.unbounded)return 0;let s=0;for(const t of e.sfc.styles){if(!t.scoped)continue;let e;try{e=r(t.content)}catch{continue}let i=0;if(e.each(e=>{"rule"===e.type&&be(e,o.classes)&&(e.remove(),i+=1)}),i>0){const o=t.base+t.content.length;n.overwrite(t.base,o,e.toString()),s+=i}}return s}function be(e,t){let n=!1;try{s(e=>{let o=!1,r=!0,s=!1;e.each(e=>{s=!0;const n=[];e.walkPseudos(e=>{const t=e.value;(t.includes("deep")||t.includes("global")||t.includes("slotted"))&&(o=!0)}),e.walkClasses(e=>{n.push(e.value)});n.some(e=>!t.has(e))||(r=!1)}),n=s&&r&&!o}).processSync(e.selector)}catch{return!1}return n}function xe(e,t){const n=new Map,r=new Map;for(const s of e.values()){const e=new o(s.code);n.set(s.id,e);const i=t.get(s.id);r.set(s.id,i.bail?new Set:Ie(s,i.constFold,i.narrow,i,e))}for(const t of e.values())De(t,r,n.get(t.id));const s={};for(const t of e.values())s[t.id]=n.get(t.id).toString();return s}function Ie(e,t,n,o,r){if(0===t.size&&0===n.size)return new Set;!function(e,t,n,o,r){const s=e.code;h(e.sfc.template,e=>{for(const i of R(e)){if(C(i[0].el,r))continue;const e=O(i,t,n);for(const[t,n]of e.removed)Ee(s,t,n,o),r.push([t,n]);for(const[t,n]of e.dirRemovals)Ee(s,t,n,o);if(e.promotion){const{from:t,to:n,text:r}=e.promotion;o.overwrite(t,n,r)}}})}(e,t,n,r,[]);const s=function(e,t,n){const o=new Set,r=e.sfc.scriptSetup;if(!(e.props&&0!==t.size&&r&&e.propsDeclaration&&e.propsPattern))return o;const s=r.base,i=e.props.filter(e=>t.has(e.name));if(0===i.length)return o;for(const e of i)o.add(e.name);const c=i.map(e=>{return`const ${e.name} = ${n=t.get(e.name),void 0===n?"undefined":JSON.stringify(n)};`;var n}).join("\n"),a=e.props.filter(e=>!t.has(e.name)),u=s+(e.propsDeclaration.start??0);let f=s+(e.propsDeclaration.end??0);";"===e.code[f]&&(f+=1);if(0===a.length&&!e.hasRestProp)return n.overwrite(u,f,c),o;const l=e.propsPattern?.properties??[];for(const t of i)Pe(l,t.property,s,n,e.code),Me(e.definePropsCall,t.name,s,n,e.code);return n.appendLeft(f,`\n${c}`),o}(e,t,r);return Se(e,{...o,constFold:t,narrow:n},r),s}function Ee(e,t,n,o){let r=t;for(;r>0&&(" "===e[r-1]||"\t"===e[r-1]);)r-=1;o.remove(r,n)}function Pe(e,t,n,o,r){const s=e.indexOf(t);Le(t,e[s-1],n,o,r)}function Me(e,t,n,o,r){const s=e?.typeParameters?.params?.[0],i=s?.members??[],c=i.findIndex(e=>"Identifier"===e.key?.type&&e.key.name===t);-1!==c&&Le(i[c],i[c-1],n,o,r)}function Le(e,t,n,o,r){const s=n+(e.start??0),i=n+(e.end??0);let c=i;for(;c<r.length&&(" "===r[c]||"\t"===r[c]);)c+=1;if(","===r[c]){let e=c+1;for(;e<r.length&&(" "===r[e]||"\t"===r[e]||"\n"===r[e]);)e+=1;return void o.remove(s,e)}t?o.remove(n+(t.end??0),i):o.remove(s,i)}function De(e,t,n){const o=e.code;g(e.sfc.template,r=>{if(!p(r)||!r.tag)return;const s=e.imports.get(r.tag),i=s?t.get(s):void 0;if(i&&0!==i.size)for(const e of r.props??[])if(e.type===u)e.name&&i.has(e.name)&&je(o,e,n);else if(e.type===f&&"bind"===e.name){const t=w(e.arg);t&&i.has(t)&&Re(e)&&je(o,e,n)}})}function Re(e){const t=w(e.exp);if(null==t)return!1;const n=v(t);return null!=n&&b(n,new Map).known}function je(e,t,n){const o=t.loc;let r=o.start.offset;for(;r>0&&(" "===e[r-1]||"\t"===e[r-1]||"\n"===e[r-1]);)r-=1;n.remove(r,o.end.offset)}async function Oe(e,t,n){const{models:o,plans:r}=await $(e,t,n);return xe(o,r)}export{$ as analyze,q as analyzeInput,T as buildAnalyzeInput,xe as transformAll,Oe as vueShaker};
|
package/dist/scan.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import*as e from"node:fs";import*as t from"node:path";const n=(e,n)=>e.startsWith(".")?t.resolve(t.dirname(n),e):null,o=t=>e.readFileSync(t,"utf-8");function r(n){const o=[];let s;try{s=e.readdirSync(n,{withFileTypes:!0})}catch{return o}for(const e of s){if("node_modules"===e.name||e.name.startsWith("."))continue;const s=t.join(n,e.name);e.isDirectory()?o.push(...r(s)):e.isFile()&&e.name.endsWith(".vue")&&o.push(s)}return o}export{r as collectVueFiles,o as fsReadFile,n as fsResolve};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { type AnyNode, type ParseCache, type ParsedSfc } from './parse';
|
|
2
|
+
import { type AnalyzeInput, type ComponentId, type ComponentPlan, type Literal } from './ir';
|
|
3
|
+
import { type Span } from './dead';
|
|
4
|
+
export type Resolve = (source: string, importer: ComponentId) => Promise<ComponentId | null> | ComponentId | null;
|
|
5
|
+
export type ReadFile = (id: ComponentId) => Promise<string> | string;
|
|
6
|
+
/** One declared prop in a `defineProps` destructuring. */
|
|
7
|
+
export interface PropDecl {
|
|
8
|
+
name: string;
|
|
9
|
+
/** The `ObjectProperty` node inside the destructure pattern (for removal). */
|
|
10
|
+
property: AnyNode;
|
|
11
|
+
/** Default value expression (inline `= d` or via `withDefaults`), if any. */
|
|
12
|
+
defaultExpr?: AnyNode | undefined;
|
|
13
|
+
}
|
|
14
|
+
/** Everything we learn from parsing one component, reused by the transform. */
|
|
15
|
+
export interface FileModel {
|
|
16
|
+
id: ComponentId;
|
|
17
|
+
code: string;
|
|
18
|
+
sfc: ParsedSfc;
|
|
19
|
+
/** local import name (`Child`) -> resolved child component id. */
|
|
20
|
+
imports: Map<string, ComponentId>;
|
|
21
|
+
/** Declared props, or `null` if the component has no destructured `defineProps`. */
|
|
22
|
+
props: PropDecl[] | null;
|
|
23
|
+
/** The `const { … } = defineProps()` declarator's ObjectPattern, for editing. */
|
|
24
|
+
propsPattern?: AnyNode | undefined;
|
|
25
|
+
/** The whole `VariableDeclaration` statement (absolute span via scriptBase). */
|
|
26
|
+
propsDeclaration?: AnyNode | undefined;
|
|
27
|
+
/** The `defineProps<{…}>()` call, whose type args hold the prop type members. */
|
|
28
|
+
definePropsCall?: AnyNode | undefined;
|
|
29
|
+
hasRestProp: boolean;
|
|
30
|
+
/** True when the destructure shares its statement with other declarators. */
|
|
31
|
+
sharesStatement: boolean;
|
|
32
|
+
/** Every `<Child .../>` this component renders. */
|
|
33
|
+
childCalls: ChildCall[];
|
|
34
|
+
/** Names bound by a template/script scope that collide with a prop name. */
|
|
35
|
+
shadowedNames: Set<string>;
|
|
36
|
+
/** Child ids leaked as a value (`<component :is="Child">`, value use). */
|
|
37
|
+
escapedComponents: Set<ComponentId>;
|
|
38
|
+
/** Child ids rendered through a barrel/named import (sites unobservable). */
|
|
39
|
+
barrelChildIds: Set<ComponentId>;
|
|
40
|
+
bailReasons: string[];
|
|
41
|
+
}
|
|
42
|
+
/** One `<Child .../>` instance rendered by a component. */
|
|
43
|
+
export interface ChildCall {
|
|
44
|
+
childId: ComponentId;
|
|
45
|
+
node: AnyNode;
|
|
46
|
+
}
|
|
47
|
+
/** One value passed explicitly to a prop at one call site (last-write-wins). */
|
|
48
|
+
export interface ExplicitProp {
|
|
49
|
+
value: Literal;
|
|
50
|
+
dynamic: boolean;
|
|
51
|
+
afterLastSpread: boolean;
|
|
52
|
+
}
|
|
53
|
+
/** How a child component is called at one `<Child .../>` site. */
|
|
54
|
+
export interface CallSite {
|
|
55
|
+
hadSpread: boolean;
|
|
56
|
+
explicit: Map<string, ExplicitProp>;
|
|
57
|
+
}
|
|
58
|
+
export interface AnalyzeResult {
|
|
59
|
+
models: Map<ComponentId, FileModel>;
|
|
60
|
+
plans: Map<ComponentId, ComponentPlan>;
|
|
61
|
+
}
|
|
62
|
+
export declare function analyze(entries: ComponentId | ComponentId[], resolve: Resolve, readFile: ReadFile): Promise<AnalyzeResult>;
|
|
63
|
+
/**
|
|
64
|
+
* The pure, environment-free engine entry (docs/RUST-MIGRATION.md §2): given a
|
|
65
|
+
* fully-resolved, batched {@link AnalyzeInput}, build every component's model and
|
|
66
|
+
* compute its plan to a whole-program fixpoint (docs §2.1). No IO/resolution.
|
|
67
|
+
*/
|
|
68
|
+
export declare function analyzeInput(input: AnalyzeInput, parseCache?: ParseCache): AnalyzeResult;
|
|
69
|
+
/**
|
|
70
|
+
* The Shell-side resolution + IO layer (docs/RUST-MIGRATION.md §2.1): BFS-crawl
|
|
71
|
+
* the component graph from `entries`, resolving every import edge and reading
|
|
72
|
+
* every reachable `.vue` file up front into a batched {@link AnalyzeInput}.
|
|
73
|
+
*/
|
|
74
|
+
export declare function buildAnalyzeInput(entries: ComponentId | ComponentId[], resolve: Resolve, readFile: ReadFile, parseCache?: ParseCache): Promise<AnalyzeInput>;
|
|
75
|
+
export declare function deadSpansForPlans(models: Map<ComponentId, FileModel>, plans: Map<ComponentId, ComponentPlan>): Map<ComponentId, Span[]>;
|
|
76
|
+
/**
|
|
77
|
+
* Read one `<Child .../>` into a {@link CallSite}. Props are in source order, so
|
|
78
|
+
* last-write-wins; a `v-bind="obj"` spread (or dynamic `:[key]`) makes the site's
|
|
79
|
+
* unset props Unknown (docs §4.1).
|
|
80
|
+
*/
|
|
81
|
+
export declare function readCallSite(component: AnyNode, _scriptBase: number): CallSite;
|
|
82
|
+
export declare function isFoldBlockedName(model: FileModel, name: string): boolean;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type MagicString from 'magic-string';
|
|
2
|
+
import type { ComponentPlan } from './ir';
|
|
3
|
+
import type { FileModel } from './analyze';
|
|
4
|
+
export interface PossibleClasses {
|
|
5
|
+
classes: Set<string>;
|
|
6
|
+
unbounded: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Compute the component's possible class set (docs §3, step 1). Sources: static
|
|
10
|
+
* `class="a b"`, `:class` with foldable/narrowable expressions, and object/array
|
|
11
|
+
* `:class` syntaxes (object keys are always-possible classes). Any spread or
|
|
12
|
+
* non-enumerable `:class` makes the set unbounded.
|
|
13
|
+
*/
|
|
14
|
+
export declare function computePossibleClasses(model: FileModel, plan: ComponentPlan): PossibleClasses;
|
|
15
|
+
/**
|
|
16
|
+
* Remove provably-dead rules from each `<style scoped>` block by re-stringifying
|
|
17
|
+
* the block without them (postcss preserves the raws of surviving rules, so the
|
|
18
|
+
* remaining CSS keeps its original formatting). Returns the number removed.
|
|
19
|
+
*/
|
|
20
|
+
export declare function shakeCss(model: FileModel, plan: ComponentPlan, s: MagicString): number;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type AnyNode } from './parse';
|
|
2
|
+
import type { Literal } from './ir';
|
|
3
|
+
export type Span = [number, number];
|
|
4
|
+
export type CondKind = 'if' | 'else-if' | 'else';
|
|
5
|
+
/** One arm of a `v-if` chain: its element, its conditional directive, its test. */
|
|
6
|
+
interface Arm {
|
|
7
|
+
el: AnyNode;
|
|
8
|
+
dir: AnyNode;
|
|
9
|
+
kind: CondKind;
|
|
10
|
+
/** Parsed test expression (undefined for `v-else` or an unparsable exp). */
|
|
11
|
+
testAst: AnyNode | undefined;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The outcome of folding one chain against the known environments.
|
|
15
|
+
*
|
|
16
|
+
* `removed` are element spans deleted from the output (dead arms).
|
|
17
|
+
* `dirRemovals` are directive spans deleted because their element now renders
|
|
18
|
+
* unconditionally (a `v-if`/`v-else-if` proven true, or a surviving `v-else`).
|
|
19
|
+
* `promotion`, when present, rewrites a surviving `v-else-if` head to `v-if`.
|
|
20
|
+
*/
|
|
21
|
+
export interface ChainDecision {
|
|
22
|
+
removed: Span[];
|
|
23
|
+
dirRemovals: Span[];
|
|
24
|
+
promotion?: {
|
|
25
|
+
from: number;
|
|
26
|
+
to: number;
|
|
27
|
+
text: string;
|
|
28
|
+
} | undefined;
|
|
29
|
+
}
|
|
30
|
+
/** The conditional directive on an element, if any. */
|
|
31
|
+
export declare function conditionalOf(el: AnyNode): {
|
|
32
|
+
dir: AnyNode;
|
|
33
|
+
kind: CondKind;
|
|
34
|
+
} | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Collect every `v-if` chain in one children array. A chain begins at an
|
|
37
|
+
* element carrying `v-if` and extends across following siblings (skipping blank
|
|
38
|
+
* text / comments) that carry `v-else-if`, ending at a `v-else` or the first
|
|
39
|
+
* sibling that continues no chain.
|
|
40
|
+
*/
|
|
41
|
+
export declare function collectChains(children: AnyNode[]): Arm[][];
|
|
42
|
+
/**
|
|
43
|
+
* Decide how one chain folds against `env` (constFold) and `setEnv` (value
|
|
44
|
+
* sets). Soundness: an arm is dropped only when its test is provably FALSE for
|
|
45
|
+
* every reachable value; the chain collapses to one arm only when that arm is
|
|
46
|
+
* provably TRUE and every earlier arm is provably FALSE. Otherwise the chain is
|
|
47
|
+
* kept (with provably-dead later arms removed).
|
|
48
|
+
*/
|
|
49
|
+
export declare function decideChain(arms: Arm[], env: Map<string, Literal>, setEnv: Map<string, Literal[]>): ChainDecision;
|
|
50
|
+
/** Is `span` fully contained in any of `spans`? */
|
|
51
|
+
export declare function inSpans(span: Span, spans: Span[]): boolean;
|
|
52
|
+
/** Is the node's template span inside any dead span? */
|
|
53
|
+
export declare function nodeInSpans(node: AnyNode, spans: Span[]): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Element spans that genuinely vanish from a component's output when its plan is
|
|
56
|
+
* applied (dead `v-if` arms). The SAME predicate the transform uses, so a call
|
|
57
|
+
* site is excluded from a child's prop profile iff the transform would actually
|
|
58
|
+
* delete it (docs §2.1 cascade).
|
|
59
|
+
*/
|
|
60
|
+
export declare function computeDeadSpans(template: AnyNode | undefined, env: Map<string, Literal>, setEnv: Map<string, Literal[]>): Span[];
|
|
61
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AnyNode } from './parse';
|
|
2
|
+
import type { Literal } from './ir';
|
|
3
|
+
export type EvalResult = {
|
|
4
|
+
known: true;
|
|
5
|
+
value: Literal;
|
|
6
|
+
} | {
|
|
7
|
+
known: false;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* A deliberately tiny, total constant evaluator over an ESTree/Babel expression,
|
|
11
|
+
* given an environment of statically-known identifiers. It never throws and
|
|
12
|
+
* never guesses: anything it cannot prove is `{ known: false }`.
|
|
13
|
+
*
|
|
14
|
+
* Vue parses `<script setup>` and template expressions with `@babel/parser`, so
|
|
15
|
+
* literals arrive as Babel's `StringLiteral` / `NumericLiteral` / … rather than
|
|
16
|
+
* ESTree's single `Literal`; both spellings are accepted here.
|
|
17
|
+
*
|
|
18
|
+
* This is the M0 stand-in for the abstract-interpretation engine described in
|
|
19
|
+
* docs/ARCHITECTURE.md §13 — same contract (sound over-approximation, falls to
|
|
20
|
+
* unknown on non-distributive ops), just without the interprocedural lattice.
|
|
21
|
+
*/
|
|
22
|
+
export declare function evaluate(node: AnyNode | null | undefined, env: Map<string, Literal>): EvalResult;
|
|
23
|
+
/**
|
|
24
|
+
* Sound set-aware predicate. `constEnv` holds props collapsed to a single
|
|
25
|
+
* literal (`constFold`); `setEnv` holds props whose reachable value set is known
|
|
26
|
+
* (`narrow`, >= 2 literals). Returns `{ known:true }` ONLY when the boolean is
|
|
27
|
+
* provable for the whole reachable set — a value reachable through the set keeps
|
|
28
|
+
* the branch. Anything outside the small supported fragment is `{ known:false }`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function evaluateWithSets(node: AnyNode | null | undefined, constEnv: Map<string, Literal>, setEnv: Map<string, Literal[]>): EvalResult;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ReadFile, type Resolve } from './analyze';
|
|
2
|
+
import type { ComponentId } from './ir';
|
|
3
|
+
export type { ComponentId, AnalyzeInput, InputFile, ResolvedEdge, EdgeKind, EditResult, ComponentPlan, PropAbstraction, PropValueSet, Literal, } from './ir';
|
|
4
|
+
export type { Resolve, ReadFile, FileModel, AnalyzeResult } from './analyze';
|
|
5
|
+
export { analyze, analyzeInput, buildAnalyzeInput } from './analyze';
|
|
6
|
+
export { transformAll } from './transform';
|
|
7
|
+
/**
|
|
8
|
+
* Whole-program shake: crawl the component graph from `entries`, decide what to
|
|
9
|
+
* fold, and return the shaken source for every reachable `.vue` file.
|
|
10
|
+
*
|
|
11
|
+
* `resolve` / `readFile` are injected so the engine stays environment-free — it
|
|
12
|
+
* has NO `node:*` imports, so it runs unchanged in the browser (the playground
|
|
13
|
+
* passes an in-memory file map). A Vite plugin passes `this.resolve`; Node
|
|
14
|
+
* callers use `fsResolve` / `fsReadFile` from `vue-shaker/node`. See
|
|
15
|
+
* docs/ARCHITECTURE.md §5 — this is the Engine; the Shell owns resolution.
|
|
16
|
+
*/
|
|
17
|
+
export declare function vueShaker(entries: ComponentId | ComponentId[], resolve: Resolve, readFile: ReadFile): Promise<Record<ComponentId, string>>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/** Resolved absolute path of a `.vue` file. */
|
|
2
|
+
export type ComponentId = string;
|
|
3
|
+
/** A statically-known literal value a prop can take. */
|
|
4
|
+
export type Literal = string | number | boolean | null | undefined;
|
|
5
|
+
/** How an imported local name binds to a child `.vue` component. */
|
|
6
|
+
export type EdgeKind = 'default-vue' | 'barrel';
|
|
7
|
+
/** One reachable `.vue` source the engine will model. */
|
|
8
|
+
export interface InputFile {
|
|
9
|
+
id: ComponentId;
|
|
10
|
+
code: string;
|
|
11
|
+
}
|
|
12
|
+
/** One resolved import edge: `from` binds `local` to the child `.vue` `to`. */
|
|
13
|
+
export interface ResolvedEdge {
|
|
14
|
+
from: ComponentId;
|
|
15
|
+
local: string;
|
|
16
|
+
to: ComponentId;
|
|
17
|
+
kind: EdgeKind;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The fully-resolved, batched input to the engine (docs §2.1). `files` is every
|
|
21
|
+
* reachable `.vue` (barrel `.js`/`.ts` are consumed during resolution and do not
|
|
22
|
+
* appear here); `edges` are already resolved to absolute ids; `entries` is the
|
|
23
|
+
* call-site-completeness set (the Shell's FS scan) and the L2 net-win roots.
|
|
24
|
+
*/
|
|
25
|
+
export interface AnalyzeInput {
|
|
26
|
+
files: InputFile[];
|
|
27
|
+
edges: ResolvedEdge[];
|
|
28
|
+
entries: ComponentId[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The delta the dev engine returns after applying file changes (docs §2.1, the
|
|
32
|
+
* `vite dev` incremental path). `changed` maps each component whose SLIMMED
|
|
33
|
+
* OUTPUT changed to its new source — a SUPERSET of the edited files, because a
|
|
34
|
+
* call-site edit can change a child's residual without the child being touched
|
|
35
|
+
* (the HMR module-graph divergence the Shell must widen for). `removed` lists
|
|
36
|
+
* components no longer in the program (deleted or now unreachable).
|
|
37
|
+
*/
|
|
38
|
+
export interface EditResult {
|
|
39
|
+
changed: Record<ComponentId, string>;
|
|
40
|
+
removed: ComponentId[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The join, over every call site in the program, of the value passed to a
|
|
44
|
+
* single prop. See the lattice in docs/ARCHITECTURE.md §2.2.
|
|
45
|
+
*
|
|
46
|
+
* M0 only ever produces `const` (single literal across all sites) and `top`
|
|
47
|
+
* (something we cannot reason about — never fold). `multi` / `dynamic` are
|
|
48
|
+
* declared now so L1.5 narrowing can be added without reshaping callers.
|
|
49
|
+
*/
|
|
50
|
+
export type PropAbstraction = {
|
|
51
|
+
kind: 'bottom';
|
|
52
|
+
} | {
|
|
53
|
+
kind: 'const';
|
|
54
|
+
value: Literal;
|
|
55
|
+
} | {
|
|
56
|
+
kind: 'multi';
|
|
57
|
+
values: Literal[];
|
|
58
|
+
} | {
|
|
59
|
+
kind: 'dynamic';
|
|
60
|
+
} | {
|
|
61
|
+
kind: 'top';
|
|
62
|
+
reason: string;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* The set of literal values one declared prop is seen to take across the whole
|
|
66
|
+
* program (default included for sites that omit it), plus the two ways it can
|
|
67
|
+
* escape the lattice. This is the value-set foundation later levels narrow on
|
|
68
|
+
* (docs §2.2 `multi`, §3 L1.5): `constFold` is just the `size === 1 && !dynamic
|
|
69
|
+
* && !top` projection of it. Kept on the plan as groundwork.
|
|
70
|
+
*/
|
|
71
|
+
export interface PropValueSet {
|
|
72
|
+
/** Distinct literals observed (dedup'd; `undefined`/`null` are distinct). */
|
|
73
|
+
values: Literal[];
|
|
74
|
+
/** A non-literal value was passed somewhere (used, value not statically known). */
|
|
75
|
+
dynamic: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* ⊤: a call-site `v-bind` spread may set this prop (docs §4.1 partial bail), so
|
|
78
|
+
* the value set is really "all values" and the prop must not be folded.
|
|
79
|
+
*/
|
|
80
|
+
top: boolean;
|
|
81
|
+
}
|
|
82
|
+
/** What the analysis decides to do to one component. */
|
|
83
|
+
export interface ComponentPlan {
|
|
84
|
+
id: ComponentId;
|
|
85
|
+
/** Whole-component bail (escape / `<component :is>` / barrel). */
|
|
86
|
+
bail: boolean;
|
|
87
|
+
reasons: string[];
|
|
88
|
+
/**
|
|
89
|
+
* L0/L1: props that collapse to a single constant. Under the "攻め"
|
|
90
|
+
* default (docs §12-2) these are folded in the body, dropped from the
|
|
91
|
+
* `defineProps` signature, and their attributes are removed at every call site.
|
|
92
|
+
*/
|
|
93
|
+
constFold: Map<string, Literal>;
|
|
94
|
+
/**
|
|
95
|
+
* L1.5 value-set narrowing (docs §3): props whose reachable value set is a
|
|
96
|
+
* known set of >= 2 distinct literals (no `dynamic`/`top` contribution). We
|
|
97
|
+
* delete branches the prop can provably never reach (e.g. a `variant ===
|
|
98
|
+
* 'danger'` arm when `variant ∈ {'primary','secondary'}`), but — unlike
|
|
99
|
+
* `constFold` — the prop is still genuinely used/dynamic, so it is NOT
|
|
100
|
+
* substituted and NOT dropped from the `defineProps` signature. Singletons
|
|
101
|
+
* stay in `constFold`; these two maps are disjoint.
|
|
102
|
+
*/
|
|
103
|
+
narrow: Map<string, Literal[]>;
|
|
104
|
+
/**
|
|
105
|
+
* Per-declared-prop value-set foundation (see {@link PropValueSet}). Present
|
|
106
|
+
* for every declared prop the analysis reasoned about; `constFold` is its
|
|
107
|
+
* singleton projection and `narrow` is its multi-element projection.
|
|
108
|
+
*/
|
|
109
|
+
valueSets: Map<string, PropValueSet>;
|
|
110
|
+
}
|
|
111
|
+
export declare function emptyPlan(id: ComponentId): ComponentPlan;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
export interface Pos {
|
|
2
|
+
offset: number;
|
|
3
|
+
line?: number | undefined;
|
|
4
|
+
column?: number | undefined;
|
|
5
|
+
}
|
|
6
|
+
export interface Loc {
|
|
7
|
+
start: Pos;
|
|
8
|
+
end: Pos;
|
|
9
|
+
source?: string | undefined;
|
|
10
|
+
}
|
|
11
|
+
export interface AnyNode {
|
|
12
|
+
/** Numeric for Vue template nodes (NodeTypes), string for Babel nodes. */
|
|
13
|
+
type: string | number;
|
|
14
|
+
start?: number | undefined;
|
|
15
|
+
end?: number | undefined;
|
|
16
|
+
loc?: Loc | undefined;
|
|
17
|
+
operator?: string | undefined;
|
|
18
|
+
left?: AnyNode | undefined;
|
|
19
|
+
right?: AnyNode | undefined;
|
|
20
|
+
argument?: AnyNode | undefined;
|
|
21
|
+
test?: AnyNode | undefined;
|
|
22
|
+
consequent?: AnyNode | undefined;
|
|
23
|
+
alternate?: AnyNode | null | undefined;
|
|
24
|
+
callee?: AnyNode | undefined;
|
|
25
|
+
arguments?: AnyNode[] | undefined;
|
|
26
|
+
object?: AnyNode | undefined;
|
|
27
|
+
property?: AnyNode | undefined;
|
|
28
|
+
computed?: boolean | undefined;
|
|
29
|
+
shorthand?: boolean | undefined;
|
|
30
|
+
expressions?: AnyNode[] | undefined;
|
|
31
|
+
quasis?: AnyNode[] | undefined;
|
|
32
|
+
elements?: (AnyNode | null)[] | undefined;
|
|
33
|
+
properties?: AnyNode[] | undefined;
|
|
34
|
+
key?: AnyNode | undefined;
|
|
35
|
+
id?: AnyNode | undefined;
|
|
36
|
+
init?: AnyNode | null | undefined;
|
|
37
|
+
declarations?: AnyNode[] | undefined;
|
|
38
|
+
declaration?: AnyNode | null | undefined;
|
|
39
|
+
specifiers?: AnyNode[] | undefined;
|
|
40
|
+
local?: AnyNode | undefined;
|
|
41
|
+
imported?: AnyNode | undefined;
|
|
42
|
+
exported?: AnyNode | undefined;
|
|
43
|
+
source?: AnyNode | undefined;
|
|
44
|
+
params?: AnyNode[] | undefined;
|
|
45
|
+
body?: AnyNode | AnyNode[] | undefined;
|
|
46
|
+
typeParameters?: AnyNode | undefined;
|
|
47
|
+
typeAnnotation?: AnyNode | undefined;
|
|
48
|
+
members?: AnyNode[] | undefined;
|
|
49
|
+
program?: AnyNode | undefined;
|
|
50
|
+
tag?: string | undefined;
|
|
51
|
+
/** ElementTypes: 0=ELEMENT 1=COMPONENT 2=SLOT 3=TEMPLATE. */
|
|
52
|
+
tagType?: number | undefined;
|
|
53
|
+
isSelfClosing?: boolean | undefined;
|
|
54
|
+
props?: AnyNode[] | undefined;
|
|
55
|
+
children?: AnyNode[] | undefined;
|
|
56
|
+
/** SimpleExpressionNode `arg`/`exp` (DirectiveNode) — has `.content` string. */
|
|
57
|
+
arg?: AnyNode | undefined;
|
|
58
|
+
exp?: AnyNode | undefined;
|
|
59
|
+
modifiers?: unknown[] | undefined;
|
|
60
|
+
isStatic?: boolean | undefined;
|
|
61
|
+
/** Vue's precompiled Babel AST for a complex expression (may be absent). */
|
|
62
|
+
ast?: AnyNode | false | null | undefined;
|
|
63
|
+
/** Babel: `Identifier.name`/import name; Vue: DirectiveNode name (`if`,`bind`). */
|
|
64
|
+
name?: string | undefined;
|
|
65
|
+
/** Babel literal value; Vue AttributeNode `value` is itself a TextNode. */
|
|
66
|
+
value?: unknown;
|
|
67
|
+
/** Vue TextNode / SimpleExpressionNode string, or InterpolationNode child. */
|
|
68
|
+
content?: unknown;
|
|
69
|
+
}
|
|
70
|
+
export declare const VueNode: {
|
|
71
|
+
readonly ROOT: 0;
|
|
72
|
+
readonly ELEMENT: 1;
|
|
73
|
+
readonly TEXT: 2;
|
|
74
|
+
readonly COMMENT: 3;
|
|
75
|
+
readonly SIMPLE_EXPRESSION: 4;
|
|
76
|
+
readonly INTERPOLATION: 5;
|
|
77
|
+
readonly ATTRIBUTE: 6;
|
|
78
|
+
readonly DIRECTIVE: 7;
|
|
79
|
+
};
|
|
80
|
+
export declare const ElementType: {
|
|
81
|
+
readonly ELEMENT: 0;
|
|
82
|
+
readonly COMPONENT: 1;
|
|
83
|
+
readonly SLOT: 2;
|
|
84
|
+
readonly TEMPLATE: 3;
|
|
85
|
+
};
|
|
86
|
+
export declare const isElementNode: (n: AnyNode) => boolean;
|
|
87
|
+
export declare const isComponentNode: (n: AnyNode) => boolean;
|
|
88
|
+
export declare const isDirective: (n: AnyNode) => boolean;
|
|
89
|
+
export declare const isAttribute: (n: AnyNode) => boolean;
|
|
90
|
+
export declare const isInterpolation: (n: AnyNode) => boolean;
|
|
91
|
+
export declare const isText: (n: AnyNode) => boolean;
|
|
92
|
+
/** Absolute SFC span of a Vue template node. */
|
|
93
|
+
export declare function tspan(n: AnyNode): [number, number];
|
|
94
|
+
/** One `<script setup>` block, with the base offset to map Babel -> SFC. */
|
|
95
|
+
export interface ScriptSetup {
|
|
96
|
+
content: string;
|
|
97
|
+
/** Absolute SFC offset where `content` begins (loc.start.offset). */
|
|
98
|
+
base: number;
|
|
99
|
+
/** Absolute SFC offset where `content` ends (loc.end.offset). */
|
|
100
|
+
endOffset: number;
|
|
101
|
+
/** Babel `Program` node (its `.body` are the top-level statements). */
|
|
102
|
+
ast: AnyNode;
|
|
103
|
+
lang: string | undefined;
|
|
104
|
+
}
|
|
105
|
+
/** One `<style>` block. */
|
|
106
|
+
export interface StyleBlock {
|
|
107
|
+
content: string;
|
|
108
|
+
/** Absolute SFC offset where the style content begins. */
|
|
109
|
+
base: number;
|
|
110
|
+
scoped: boolean;
|
|
111
|
+
}
|
|
112
|
+
/** Everything one `.vue` SFC parse yields, in this engine's loose shape. */
|
|
113
|
+
export interface ParsedSfc {
|
|
114
|
+
id: string;
|
|
115
|
+
code: string;
|
|
116
|
+
/** Vue template RootNode (`descriptor.template.ast`), or undefined. */
|
|
117
|
+
template: AnyNode | undefined;
|
|
118
|
+
/** Parsed `<script setup>`, or undefined (Options API / plain script). */
|
|
119
|
+
scriptSetup: ScriptSetup | undefined;
|
|
120
|
+
/** True when the SFC has a non-setup `<script>` or no `<script setup>`. */
|
|
121
|
+
hasPlainScript: boolean;
|
|
122
|
+
styles: StyleBlock[];
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Parse one `.vue` SFC into the engine's loose model. Throws nothing the caller
|
|
126
|
+
* must handle beyond Vue's own parse errors (a malformed SFC); the Shell decides
|
|
127
|
+
* whether to pass such files through untouched.
|
|
128
|
+
*/
|
|
129
|
+
export declare function parseVue(code: string, filename: string): ParsedSfc;
|
|
130
|
+
/**
|
|
131
|
+
* Content-keyed parse cache: a hit returns the IDENTICAL parse for unchanged
|
|
132
|
+
* source, so the dev engine re-parses only files that actually changed
|
|
133
|
+
* (docs/RUST-MIGRATION.md §2.2). Keyed by content, so a stale entry can never
|
|
134
|
+
* return offsets that disagree with the source.
|
|
135
|
+
*/
|
|
136
|
+
export type ParseCache = Map<string, {
|
|
137
|
+
code: string;
|
|
138
|
+
sfc: ParsedSfc;
|
|
139
|
+
}>;
|
|
140
|
+
export declare function parseCached(filename: string, code: string, cache?: ParseCache): ParsedSfc;
|
|
141
|
+
/**
|
|
142
|
+
* Parse a Vue template expression string into a Babel AST. Offsets on the
|
|
143
|
+
* returned node are RELATIVE to `content`; map to absolute SFC offsets via the
|
|
144
|
+
* owning expression node's `loc.start.offset` (which the probe confirmed aligns
|
|
145
|
+
* exactly with `content`). Returns `undefined` if the expression does not parse
|
|
146
|
+
* (the caller then treats it as non-foldable).
|
|
147
|
+
*/
|
|
148
|
+
export declare function parseExpr(content: string | undefined): AnyNode | undefined;
|
|
149
|
+
/** The `.content` string of a Vue SimpleExpressionNode (`exp`/`arg`). */
|
|
150
|
+
export declare function exprContent(node: AnyNode | undefined): string | undefined;
|
|
151
|
+
/**
|
|
152
|
+
* Depth-first walk over Vue TEMPLATE nodes (the `children` tree). `visit` is
|
|
153
|
+
* called for every node; returning `false` skips that node's children. Does NOT
|
|
154
|
+
* descend into directive expressions (those are Babel ASTs walked separately).
|
|
155
|
+
*/
|
|
156
|
+
export declare function walkTemplate(node: AnyNode | undefined, visit: (n: AnyNode) => boolean | void): void;
|
|
157
|
+
/**
|
|
158
|
+
* For every node that owns a `children` array (root + element/template nodes),
|
|
159
|
+
* call `fn(children, parent)`. This is the level at which `v-if` / `v-else-if` /
|
|
160
|
+
* `v-else` chains are folded, since in Vue those are sibling elements rather than
|
|
161
|
+
* one nested block (docs §7).
|
|
162
|
+
*/
|
|
163
|
+
export declare function eachChildList(node: AnyNode | undefined, fn: (children: AnyNode[], parent: AnyNode) => void): void;
|
|
164
|
+
/** Generic depth-first walk over a Babel AST node, with parent tracking. */
|
|
165
|
+
export declare function walkBabel(node: AnyNode | null | undefined, visit: (n: AnyNode, parent: AnyNode | null) => void, parent?: AnyNode | null): void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ComponentId } from './ir';
|
|
2
|
+
import type { Resolve, ReadFile } from './analyze';
|
|
3
|
+
/** Default filesystem resolver: resolve `source` relative to its importer. */
|
|
4
|
+
export declare const fsResolve: Resolve;
|
|
5
|
+
/** Default filesystem reader. */
|
|
6
|
+
export declare const fsReadFile: ReadFile;
|
|
7
|
+
/**
|
|
8
|
+
* Recursively collect every `.vue` file under `dir` (skipping `node_modules` and
|
|
9
|
+
* dot-directories). A Shell helper kept out of the env-free engine core: plugins
|
|
10
|
+
* use it to seed the whole-program crawl (the call-site-completeness set).
|
|
11
|
+
*/
|
|
12
|
+
export declare function collectVueFiles(dir: string): ComponentId[];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import MagicString from 'magic-string';
|
|
2
|
+
import type { ComponentId, ComponentPlan, Literal } from './ir';
|
|
3
|
+
import type { FileModel } from './analyze';
|
|
4
|
+
/**
|
|
5
|
+
* Apply every plan to every component and return the shaken source per file.
|
|
6
|
+
*
|
|
7
|
+
* Two phases over a shared set of MagicStrings so a parent's call-site
|
|
8
|
+
* attributes are removed using each child's ACTUALLY dropped props (phase 2),
|
|
9
|
+
* not just what the plan proposed (phase 1).
|
|
10
|
+
*/
|
|
11
|
+
export declare function transformAll(models: Map<ComponentId, FileModel>, plans: Map<ComponentId, ComponentPlan>): Record<ComponentId, string>;
|
|
12
|
+
/**
|
|
13
|
+
* Slim one component's body against the fold (`env`) and narrow (`setEnv`)
|
|
14
|
+
* environments, editing `s` in place, and return the set of props that left the
|
|
15
|
+
* `defineProps` signature.
|
|
16
|
+
*
|
|
17
|
+
* Vue strategy (docs §7): a folded prop is DEMOTED from the destructure to a
|
|
18
|
+
* local `const name = <value>`, rather than substituted at every reference. Vue
|
|
19
|
+
* resolves template/script identifiers to setup bindings, so the demoted const
|
|
20
|
+
* keeps every reference valid with no dangling identifier — and the prop still
|
|
21
|
+
* leaves the public signature, so call sites can drop the attribute. This avoids
|
|
22
|
+
* the substitution-completeness hazard (a missed reference would dangle).
|
|
23
|
+
*/
|
|
24
|
+
export declare function shakeBody(model: FileModel, env: Map<string, Literal>, setEnv: Map<string, Literal[]>, cssPlan: ComponentPlan, s: MagicString): Set<string>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
export interface ShakerOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Dirs (relative to the Vite root) holding EVERY `.vue` call site. Prop
|
|
5
|
+
* elimination is only sound if every consumer of a prop is in scope. Defaults
|
|
6
|
+
* to the Vite root.
|
|
7
|
+
*/
|
|
8
|
+
include?: string[];
|
|
9
|
+
/**
|
|
10
|
+
* Optimization level. L0/L1/L1.5 + CSS are always on; `2` opts into L2
|
|
11
|
+
* monomorphization (not yet implemented — accepted for forward compatibility).
|
|
12
|
+
*/
|
|
13
|
+
level?: 0 | 1 | 2;
|
|
14
|
+
/** L2 tuning; consulted only when `level: 2` (not yet implemented). */
|
|
15
|
+
monomorphize?: boolean | {
|
|
16
|
+
maxVariants?: number;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The Vite plugin Shell (docs/ARCHITECTURE.md §5/§6). It runs `enforce: 'pre'`
|
|
21
|
+
* and `apply: 'build'` so it hands already-slimmed `.vue` source to
|
|
22
|
+
* `@vitejs/plugin-vue` — and only in `vite build`, never dev (dev is a
|
|
23
|
+
* pass-through by design; whole-program analysis is incompatible with HMR).
|
|
24
|
+
*/
|
|
25
|
+
export declare function shaker(options?: ShakerOptions): Plugin;
|
package/dist/vite.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import*as e from"node:path";import{parse as t}from"@vue/compiler-sfc";import{parseExpression as n,parse as o}from"@babel/parser";import r from"magic-string";import{parse as s}from"postcss";import i from"postcss-selector-parser";import*as c from"node:fs";const a=1,u=2,f=3,l=6,p=7,d=1,m=e=>e.type===a&&e.tagType===d,y=["typescript"];function v(e,n){const{descriptor:r}=t(e,{filename:n}),s=r;let i;if(s.scriptSetup){const e=s.scriptSetup,t=e.attrs?.lang;i={content:e.content,base:e.loc.start.offset,endOffset:e.loc.end.offset,ast:(c=e.content,o(c,{sourceType:"module",plugins:[...y]}).program),lang:"string"==typeof t?t:void 0}}var c;const a=(s.styles??[]).map(e=>({content:e.content,base:e.loc.start.offset,scoped:!0===e.scoped}));return{id:n,code:e,template:s.template?.ast??void 0,scriptSetup:i,hasPlainScript:null!=s.script,styles:a}}function w(e,t,n){return v(t,e)}function h(e){if(null!=e&&""!==e.trim())try{return n(e,{plugins:[...y]})}catch{return}}function g(e){if(e)return"string"==typeof e.content?e.content:void 0}function k(e,t){if(!e)return;if(!1!==t(e))for(const n of e.children??[])k(n,t)}function S(e,t){if(!e)return;const n=e.children;if(n){t(n,e);for(const e of n)S(e,t)}}function b(e,t,n=null){if(e&&"object"==typeof e&&"string"==typeof e.type){t(e,n);for(const n of Object.values(e))if(Array.isArray(n))for(const o of n)b(o,t,e);else n&&"object"==typeof n&&"string"==typeof n.type&&b(n,t,e)}}const x={known:!1};function I(e,t){if(!e)return x;switch(e.type){case"Literal":case"StringLiteral":case"NumericLiteral":case"BooleanLiteral":return{known:!0,value:e.value};case"NullLiteral":return{known:!0,value:null};case"Identifier":{const n=e.name??"";return"undefined"===n?{known:!0,value:void 0}:t.has(n)?{known:!0,value:t.get(n)}:x}case"UnaryExpression":{const n=I(e.argument,t);if(!n.known)return x;const o=n.value;switch(e.operator){case"!":return{known:!0,value:!o};case"-":return{known:!0,value:-o};case"+":return{known:!0,value:+o};case"typeof":return{known:!0,value:typeof o};case"void":return{known:!0,value:void 0};default:return x}}case"LogicalExpression":{const n=I(e.left,t);if(!n.known)return x;switch(e.operator){case"&&":return n.value?I(e.right,t):n;case"||":return n.value?n:I(e.right,t);case"??":return null===n.value||void 0===n.value?I(e.right,t):n;default:return x}}case"BinaryExpression":{const n=I(e.left,t),o=I(e.right,t);if(!n.known||!o.known)return x;const r=n.value,s=o.value;switch(e.operator){case"===":return{known:!0,value:r===s};case"!==":return{known:!0,value:r!==s};case"==":return{known:!0,value:r==s};case"!=":return{known:!0,value:r!=s};case"<":return{known:!0,value:r<s};case">":return{known:!0,value:r>s};case"<=":return{known:!0,value:r<=s};case">=":return{known:!0,value:r>=s};case"+":return{known:!0,value:r+s};case"-":return{known:!0,value:r-s};case"*":return{known:!0,value:r*s};case"/":return{known:!0,value:r/s};case"%":return{known:!0,value:r%s};default:return x}}default:return x}}function E(e,t,n){const o=I(e,t);if(o.known)return o;const r=P(e,t,n);return"unknown"===r?x:{known:!0,value:r}}function P(e,t,n){if(!e)return"unknown";switch(e.type){case"UnaryExpression":return"!"===e.operator?L(P(e.argument,t,n)):"unknown";case"LogicalExpression":{const s=P(e.left,t,n),i=()=>P(e.right,t,n);return"&&"===e.operator?!1!==s&&(o=s,r=i(),!1!==o&&!1!==r&&(!0===o&&!0===r||"unknown")):"||"===e.operator?!0===s||function(e,t){return!0===e||!0===t||(!1!==e||!1!==t)&&"unknown"}(s,i()):"unknown"}case"BinaryExpression":{const o=e.operator;if("==="===o||"=="===o||"!=="===o||"!="===o){const r="=="===o||"!="===o,s=function(e,t,n,o,r){const s=M(e,o),i=I(t,n);if(s&&i.known)return D(s,i.value,r);const c=M(t,o),a=I(e,n);return c&&a.known?D(c,a.value,r):a.known&&i.known?r?a.value==i.value:a.value===i.value:"unknown"}(e.left,e.right,t,n,r);return"!=="===o||"!="===o?L(s):s}return"unknown"}default:return"unknown"}var o,r}function M(e,t){return"Identifier"===e?.type&&e.name&&t.has(e.name)?t.get(e.name):null}function D(e,t,n){const o=n?e=>e==t:e=>e===t;return!!e.some(o)&&(!!e.every(o)||"unknown")}function L(e){return!0!==e&&(!1===e||"unknown")}function R(e){if(e.type===a)for(const t of e.props??[])if(t.type===p){if("if"===t.name)return{dir:t,kind:"if"};if("else-if"===t.name)return{dir:t,kind:"else-if"};if("else"===t.name)return{dir:t,kind:"else"}}}const j=e=>e.type===u&&"string"==typeof e.content&&""===e.content.trim()||e.type===f;function O(e){const t=[];for(let n=0;n<e.length;n++){const o=e[n],r=R(o);if(!r||"if"!==r.kind)continue;const s=[C(o,r.dir,"if")];let i=n+1;for(;i<e.length;i++){const t=e[i];if(j(t))continue;const n=R(t);if(!n||"if"===n.kind)break;if(s.push(C(t,n.dir,n.kind)),"else"===n.kind){i++;break}}t.push(s),n=i-1}return t}function C(e,t,n){return{el:e,dir:t,kind:n,testAst:"else"===n?void 0:h(g(t.exp))}}function z(e,t,n){const o=e.map(e=>"else"===e.kind?{known:!0,value:!0}:E(e.testAst,t,n)),r=e=>e.known&&Boolean(e.value),s=e=>e.known&&!e.value,i=e=>[e.el.loc.start.offset,e.el.loc.end.offset],c=e=>[e.dir.loc.start.offset,e.dir.loc.end.offset];let a=!0;for(let t=0;t<e.length;t++){if(r(o[t])&&a){const n=[];for(let o=0;o<e.length;o++)o!==t&&n.push(i(e[o]));return{removed:n,dirRemovals:[c(e[t])]}}s(o[t])||(a=!1)}const u=o.findIndex(e=>!s(e));if(-1===u)return{removed:e.map(i),dirRemovals:[]};const f=[];for(let t=0;t<u;t++)f.push(i(e[t]));for(let t=u+1;t<e.length;t++)s(o[t])&&f.push(i(e[t]));if(0===u)return{removed:f,dirRemovals:[]};const l=e[u];if("else"===l.kind)return{removed:f,dirRemovals:[c(l)]};const p=l.dir.loc.start.offset;return{removed:f,dirRemovals:[],promotion:{from:p,to:p+9,text:"v-if"}}}function F(e,t){const n=e.loc;return!!n&&function(e,t){return t.some(([t,n])=>e[0]>=t&&e[1]<=n)}([n.start.offset,n.end.offset],t)}function A(e,t,n){if(!e||0===t.size&&0===n.size)return[];const o=[];return S(e,e=>{for(const r of O(e)){if(F(r[0].el,o))continue;const e=z(r,t,n);for(const t of e.removed)o.push(t)}}),o}const N=e=>e.endsWith(".vue"),B='escapes as value (e.g. <component :is="X">)',W="rendered through a barrel/named import (call sites unobservable)";function $(e){return e.sfc.scriptSetup?.base??0}async function T(e,t,n){return function(e){const t=function(e){const t=new Map;for(const n of e.edges){const e=t.get(n.from);e?e.push(n):t.set(n.from,[n])}const n=new Map;for(const o of e.files)n.set(o.id,G(o,t.get(o.id)??[]));return n}(e),n=new Set;for(const e of t.values())for(const t of e.escapedComponents)n.add(t);for(const e of n){const n=t.get(e);n&&!n.bailReasons.includes(B)&&n.bailReasons.push(B)}const o=new Set;for(const e of t.values())for(const t of e.barrelChildIds)o.add(t);for(const e of o){const n=t.get(e);n&&!n.bailReasons.includes(W)&&n.bailReasons.push(W)}let r=U(t,q(t,new Map));for(let e=0;e<10;e++){const e=U(t,q(t,_(t,r)));if(V(r,e)){r=e;break}r=e}return{models:t,plans:r}}(await async function(e,t,n){const o=Array.isArray(e)?[...e]:[e],r=[],s=[],i=[...o],c=new Set(i);for(;i.length>0;){const e=i.shift();let o,a;try{o=await n(e)}catch{continue}r.push({id:e,code:o});try{a=w(e,o)}catch{continue}if(!a.scriptSetup)continue;const u=new Map,f=[];for(const o of fe(a.scriptSetup.ast)){if("default"===o.imported&&N(o.value)){const n=await t(o.value,e);n&&(s.push({from:e,local:o.local,to:n,kind:"default-vue"}),f.push(n));continue}const r=await me(o.value,o.imported,e,t,n);r&&(s.push({from:e,local:o.local,to:r,kind:"barrel"}),u.set(o.local,r))}const l=Z(a.template,u);for(const e of[...f,...l])c.has(e)||(c.add(e),i.push(e))}return{files:r,edges:s,entries:o}}(e,t,n))}function q(e,t){const n=new Map,o=e=>{let t=n.get(e);return t||(t={sites:[]},n.set(e,t)),t};for(const n of e.values()){const e=t.get(n.id)??[];$(n);for(const t of n.childCalls)e.length>0&&F(t.node,e)||o(t.childId).sites.push(te(t.node))}return n}function U(e,t){const n=new Map;for(const o of e.values())n.set(o.id,ce(o,t.get(o.id)));return n}function V(e,t){if(e.size!==t.size)return!1;for(const[n,o]of e){const e=t.get(n);if(!e)return!1;if(o.bail!==e.bail)return!1;if(!J(o.constFold,e.constFold))return!1;if(!X(o.narrow,e.narrow))return!1}return!0}function J(e,t){if(e.size!==t.size)return!1;for(const[n,o]of e)if(!t.has(n)||!Object.is(t.get(n),o))return!1;return!0}function X(e,t){if(e.size!==t.size)return!1;for(const[n,o]of e){const e=t.get(n);if(!e||o.length!==e.length)return!1;for(let t=0;t<o.length;t++)if(!Object.is(o[t],e[t]))return!1}return!0}function _(e,t){const n=new Map;for(const o of e.values()){const e=t.get(o.id);if(e.bail)continue;const r=A(o.sfc.template,e.constFold,e.narrow);r.length>0&&n.set(o.id,r)}return n}function G(e,t,n){const{id:o,code:r}=e,s=w(o,r),i=new Map,c=new Map;for(const e of t)"default-vue"===e.kind?i.set(e.local,e.to):c.set(e.local,e.to);const u=[];if(s.scriptSetup)for(const e of function(e){const t=new Set;return b(e,e=>{"CallExpression"===e.type&&"Identifier"===e.callee?.type&&e.callee.name&&t.add(e.callee.name)}),t}(s.scriptSetup.ast))"defineExpose"===e&&u.push("defineExpose() (public instance surface)");let f,l,d,y=null,v=!1,S=!1;const x=new Set;if(s.scriptSetup){for(const e of fe(s.scriptSetup.ast))x.add(e.local);const e=function(e){const t=e.body??[];for(const e of t)if("VariableDeclaration"===e.type)for(const t of e.declarations??[]){const n=t.init,o=t.id;if("ObjectPattern"!==o?.type||"CallExpression"!==n?.type)continue;const r="Identifier"===n.callee?.type?n.callee.name:void 0;if("defineProps"===r)return{declaration:e,pattern:o,definePropsCall:n,withDefaults:new Map,sharesStatement:(e.declarations?.length??1)>1};if("withDefaults"===r){const t=n.arguments?.[0];if("CallExpression"!==t?.type||"Identifier"!==t.callee?.type)continue;if("defineProps"!==t.callee.name)continue;return{declaration:e,pattern:o,definePropsCall:t,withDefaults:ve(n.arguments?.[1]),sharesStatement:(e.declarations?.length??1)>1}}}return null}(s.scriptSetup.ast);if(e){l=e.declaration,f=e.pattern,d=e.definePropsCall,S=e.sharesStatement,e.sharesStatement&&u.push("defineProps() shares a multi-declarator statement"),y=[];for(const t of e.pattern.properties??[]){if("RestElement"===t.type){v=!0;continue}if("ObjectProperty"!==t.type)continue;const n=t.key;if("Identifier"!==n?.type||!n.name)continue;const o=t.value,r="AssignmentPattern"===o?.type?o.right:void 0;y.push({name:n.name,property:t,defaultExpr:r??e.withDefaults.get(n.name)})}}}const I=function(e,t){const n=[];return k(e,e=>{if(m(e)&&e.tag){const o=t.get(e.tag);o&&n.push({childId:o,node:e})}}),n}(s.template,i),E=Z(s.template,c),P=function(e){const t=new Set;k(e.template,e=>{if(e.type===a)for(const n of e.props??[])n.type===p&&("for"===n.name?H(g(n.exp),t):"slot"===n.name&&K(g(n.exp),t))});const n=e.scriptSetup;n&&b(n.ast,e=>{if("FunctionDeclaration"===e.type||"FunctionExpression"===e.type||"ArrowFunctionExpression"===e.type)for(const n of e.params??[])Q(n,t)});return t}(s),M=function(e,t,n){const o=new Set,r=e=>{if(!e)return;const n=t.get(e);n&&o.add(n)};k(e.template,e=>{if(e.type===a)for(const t of e.props??[])if(t.type===p&&"bind"===t.name&&"is"===ee(t.arg)){const e=h(g(t.exp));"Identifier"===e?.type&&r(e.name)}});const s=e.scriptSetup;s&&b(s.ast,(e,o)=>{"Identifier"===e.type&&e.name&&t.has(e.name)&&n.has(e.name)&&function(e,t){return!!t&&(!("MemberExpression"===t.type&&t.property===e&&!t.computed)&&(!!("ObjectProperty"!==t.type&&"Property"!==t.type||t.key!==e||t.computed||!0===t.shorthand)&&!Y(t)))}(e,o)&&!Y(o)&&r(e.name)});return o}(s,i,x);return{id:o,code:r,sfc:s,imports:i,props:y,propsPattern:f,propsDeclaration:l,definePropsCall:d,hasRestProp:v,sharesStatement:S,childCalls:I,shadowedNames:P,escapedComponents:M,barrelChildIds:E,bailReasons:u}}function H(e,t){if(!e)return;const n=/^(.*?)\s+(?:in|of)\s+/s.exec(e),o=(n?n[1]:e).trim();o&&K(o,t)}function K(e,t){if(!e)return;const n=h(`(${e}) => 0`);if("ArrowFunctionExpression"!==n?.type){const n=h(e);return void(n&&Q(n,t))}for(const e of n.params??[])Q(e,t)}function Q(e,t){if(e)switch(e.type){case"Identifier":return void(e.name&&t.add(e.name));case"ObjectExpression":case"ObjectPattern":for(const n of e.properties??[])"RestElement"===n.type?Q(n.argument,t):"ObjectProperty"!==n.type&&"Property"!==n.type||Q(n.value??n.key,t);return;case"ArrayExpression":case"ArrayPattern":for(const n of e.elements??[])Q(n,t);return;case"AssignmentPattern":return void Q(e.left,t);case"RestElement":return void Q(e.argument,t);default:return}}function Y(e){return null!=e&&("ImportSpecifier"===e.type||"ImportDefaultSpecifier"===e.type||"ImportNamespaceSpecifier"===e.type||"ExportSpecifier"===e.type)}function Z(e,t){const n=new Set;return 0===t.size||k(e,e=>{if(m(e)&&e.tag){const o=t.get(e.tag);o&&n.add(o)}}),n}function ee(e){return g(e)}function te(e,t){const n=e.props??[];let o=-1;for(let e=0;e<n.length;e++){const t=n[e];t.type===p&&"bind"===t.name&&ne(t)&&(o=e)}const r=new Map;for(let e=0;e<n.length;e++){const t=n[e],s=e>o;if(t.type===l){if(!t.name)continue;const e=re(t);r.set(t.name,{value:e,dynamic:!1,afterLastSpread:s});continue}if(t.type===p){if("bind"===t.name){if(ne(t))continue;const e=g(t.arg);if(!e)continue;const n=se(g(t.exp));r.set(e,n.known?{value:n.value,dynamic:!1,afterLastSpread:s}:oe(s));continue}if("model"===t.name){const e=g(t.arg)??"modelValue";r.set(e,oe(s));continue}}}return{hadSpread:o>=0,explicit:r}}function ne(e){return null==e.arg||null==g(e.arg)}function oe(e){return{value:void 0,dynamic:!0,afterLastSpread:e}}function re(e){const t=e.value;return!t||("string"==typeof t.content?t.content:"")}function se(e){const t=h(e);return t?I(t,new Map):{known:!1}}function ie(e,t){return e.shadowedNames.has(t)}function ce(e,t){const n={id:e.id,bail:!1,reasons:[],constFold:new Map,narrow:new Map,valueSets:new Map};if(e.bailReasons.length>0)return n.bail=!0,n.reasons.push(...e.bailReasons),n;if(!e.props||0===e.props.length)return n;const o=t?.sites??[];if(0===o.length)return n;for(const t of e.props){if(ie(e,t.name))continue;const r=ae(t,o);n.valueSets.set(t.name,r),r.top||r.dynamic||(1!==r.values.length?r.values.length>=2&&n.narrow.set(t.name,r.values):n.constFold.set(t.name,r.values[0]))}return n}function ae(e,t){const n=[];let o=!1,r=!1;const s=e=>{n.some(t=>Object.is(t,e))||n.push(e)};for(const n of t){const t=n.explicit.get(e.name);if(t?.afterLastSpread){t.dynamic?o=!0:s(t.value);continue}if(n.hadSpread){r=!0;continue}const i=ue(e.defaultExpr);i.known?s(i.value):o=!0}return{values:n,dynamic:o,top:r}}function ue(e){return e?I(e,new Map):{known:!0,value:void 0}}function*fe(e){const t=e.body??[];for(const e of t){if("ImportDeclaration"!==e.type)continue;const t=e.source?.value;if("string"==typeof t)for(const n of e.specifiers??[]){const e=n.local?.name;e&&("ImportDefaultSpecifier"===n.type?yield{value:t,local:e,imported:"default"}:"ImportNamespaceSpecifier"===n.type?yield{value:t,local:e,imported:"*"}:"ImportSpecifier"===n.type&&(yield{value:t,local:e,imported:le(n)??e}))}}}function le(e){const t=e.imported;return"Identifier"===t?.type&&t.name?t.name:"StringLiteral"===t?.type&&"string"==typeof t.value?t.value:void 0}function pe(e){return"Identifier"===e?.type&&e.name?e.name:"StringLiteral"===e?.type&&"string"==typeof e.value?e.value:void 0}const de=8;async function me(e,t,n,o,r,s=0){if(s>de)return null;const i=await o(e,n);if(!i)return null;if(N(e)||N(i))return"default"===t||"*"===t?i:null;let c;try{c=await r(i)}catch{return null}const a=function(e){try{const t=v(`<script setup>\n${e}\n<\/script>`,"barrel.vue");return t.scriptSetup?.ast.body??null}catch{return null}}(c);if(!a)return null;for(const e of a)if("ExportNamedDeclaration"===e.type&&e.source?.value){for(const n of e.specifiers??[])if(pe(n.exported)===t)return me(String(e.source.value),pe(n.local)??"default",i,o,r,s+1)}else if("ExportNamedDeclaration"!==e.type||e.source){if("ExportAllDeclaration"===e.type&&e.source?.value){const n=await me(String(e.source.value),t,i,o,r,s+1);if(n)return n}}else for(const n of e.specifiers??[]){if(pe(n.exported)!==t)continue;const e=pe(n.local);if(!e)continue;const c=ye(a,e);return c?me(c.value,c.imported,i,o,r,s+1):null}return null}function ye(e,t){for(const n of e){if("ImportDeclaration"!==n.type)continue;const e=n.source?.value;if("string"==typeof e)for(const o of n.specifiers??[])if(o.local?.name===t){if("ImportDefaultSpecifier"===o.type)return{value:e,imported:"default"};if("ImportNamespaceSpecifier"===o.type)return{value:e,imported:"*"};if("ImportSpecifier"===o.type)return{value:e,imported:le(o)??t}}}return null}function ve(e){const t=new Map;if("ObjectExpression"!==e?.type)return t;for(const n of e.properties??[]){if("ObjectProperty"!==n.type)continue;const e=n.key,o="Identifier"===e?.type?e.name:void 0;o&&n.value&&t.set(o,n.value)}return t}const we=Symbol("unbounded-class-source");function he(e,t,n){if(!e)return we;if("ObjectExpression"===e.type){const t=new Set;for(const n of e.properties??[]){if("ObjectProperty"!==n.type)return we;const e=n.key,o="Identifier"===e?.type?e.name:"StringLiteral"===e?.type?e.value:void 0;if(null==o)return we;for(const e of o.split(/\s+/))e&&t.add(e)}return t}if("ArrayExpression"===e.type){const o=new Set;for(const r of e.elements??[]){if(!r)continue;const e=he(r,t,n);if(e===we)return we;for(const t of e)o.add(t)}return o}const o=ge(e,t,n);if(o===we)return we;const r=new Set;for(const e of o)for(const t of e.split(/\s+/))t&&r.add(t);return r}function ge(e,t,n){if(!e)return we;switch(e.type){case"StringLiteral":case"NumericLiteral":case"BooleanLiteral":return new Set([String(e.value)]);case"Identifier":{const o=e.name??"";if(n.has(o)){const e=new Set;for(const t of n.get(o))e.add(String(t));return e}const r=I(e,t);return r.known?new Set([String(r.value)]):we}case"TemplateLiteral":{const o=e.quasis??[],r=e.expressions??[];let s=[ke(o[0])];for(let e=0;e<r.length;e++){const i=ge(r[e],t,n);if(i===we)return we;const c=ke(o[e+1]),a=[];for(const e of s)for(const t of i)if(a.push(e+t+c),a.length>64)return we;s=a}return new Set(s)}case"BinaryExpression":{if("+"!==e.operator)break;const o=ge(e.left,t,n),r=ge(e.right,t,n);if(o===we||r===we)return we;const s=new Set;for(const e of o)for(const t of r)if(s.add(e+t),s.size>64)return we;return s}case"ConditionalExpression":{const o=I(e.test,t);if(o.known)return ge(o.value?e.consequent:e.alternate,t,n);const r=ge(e.consequent,t,n),s=ge(e.alternate,t,n);return r===we||s===we?we:new Set([...r,...s])}}const o=I(e,t);return o.known?new Set([String(o.value)]):we}function ke(e){const t=e?.value;return t?.cooked??t?.raw??""}function Se(e,t,n){const o=function(e,t){const n=new Set;let o=!1;const r=t.constFold,s=t.narrow;return k(e.sfc.template,e=>{if(e.type===a)for(const t of e.props??[]){if(t.type===l){if("class"===t.name){const e=t.value,o="string"==typeof e?.content?e.content:"";for(const e of o.split(/\s+/))e&&n.add(e)}continue}if(t.type!==p||"bind"!==t.name)continue;const e=g(t.arg);if(null==t.arg||null==e){o=!0;continue}if("class"!==e)continue;const i=he(h(g(t.exp)),r,s);if(i===we)o=!0;else for(const e of i)n.add(e)}}),{classes:n,unbounded:o}}(e,t);if(o.unbounded)return 0;let r=0;for(const t of e.sfc.styles){if(!t.scoped)continue;let e;try{e=s(t.content)}catch{continue}let i=0;if(e.each(e=>{"rule"===e.type&&be(e,o.classes)&&(e.remove(),i+=1)}),i>0){const o=t.base+t.content.length;n.overwrite(t.base,o,e.toString()),r+=i}}return r}function be(e,t){let n=!1;try{i(e=>{let o=!1,r=!0,s=!1;e.each(e=>{s=!0;const n=[];e.walkPseudos(e=>{const t=e.value;(t.includes("deep")||t.includes("global")||t.includes("slotted"))&&(o=!0)}),e.walkClasses(e=>{n.push(e.value)});n.some(e=>!t.has(e))||(r=!1)}),n=s&&r&&!o}).processSync(e.selector)}catch{return!1}return n}function xe(e,t,n,o,r){if(0===t.size&&0===n.size)return new Set;!function(e,t,n,o,r){const s=e.code;S(e.sfc.template,e=>{for(const i of O(e)){if(F(i[0].el,r))continue;const e=z(i,t,n);for(const[t,n]of e.removed)Ie(s,t,n,o),r.push([t,n]);for(const[t,n]of e.dirRemovals)Ie(s,t,n,o);if(e.promotion){const{from:t,to:n,text:r}=e.promotion;o.overwrite(t,n,r)}}})}(e,t,n,r,[]);const s=function(e,t,n){const o=new Set,r=e.sfc.scriptSetup;if(!(e.props&&0!==t.size&&r&&e.propsDeclaration&&e.propsPattern))return o;const s=r.base,i=e.props.filter(e=>t.has(e.name));if(0===i.length)return o;for(const e of i)o.add(e.name);const c=i.map(e=>{return`const ${e.name} = ${n=t.get(e.name),void 0===n?"undefined":JSON.stringify(n)};`;var n}).join("\n"),a=e.props.filter(e=>!t.has(e.name)),u=s+(e.propsDeclaration.start??0);let f=s+(e.propsDeclaration.end??0);";"===e.code[f]&&(f+=1);if(0===a.length&&!e.hasRestProp)return n.overwrite(u,f,c),o;const l=e.propsPattern?.properties??[];for(const t of i)Ee(l,t.property,s,n,e.code),Pe(e.definePropsCall,t.name,s,n,e.code);return n.appendLeft(f,`\n${c}`),o}(e,t,r);return Se(e,{...o,constFold:t,narrow:n},r),s}function Ie(e,t,n,o){let r=t;for(;r>0&&(" "===e[r-1]||"\t"===e[r-1]);)r-=1;o.remove(r,n)}function Ee(e,t,n,o,r){const s=e.indexOf(t);Me(t,e[s-1],n,o,r)}function Pe(e,t,n,o,r){const s=e?.typeParameters?.params?.[0],i=s?.members??[],c=i.findIndex(e=>"Identifier"===e.key?.type&&e.key.name===t);-1!==c&&Me(i[c],i[c-1],n,o,r)}function Me(e,t,n,o,r){const s=n+(e.start??0),i=n+(e.end??0);let c=i;for(;c<r.length&&(" "===r[c]||"\t"===r[c]);)c+=1;if(","===r[c]){let e=c+1;for(;e<r.length&&(" "===r[e]||"\t"===r[e]||"\n"===r[e]);)e+=1;return void o.remove(s,e)}t?o.remove(n+(t.end??0),i):o.remove(s,i)}function De(e,t,n){const o=e.code;k(e.sfc.template,r=>{if(!m(r)||!r.tag)return;const s=e.imports.get(r.tag),i=s?t.get(s):void 0;if(i&&0!==i.size)for(const e of r.props??[])if(e.type===l)e.name&&i.has(e.name)&&Re(o,e,n);else if(e.type===p&&"bind"===e.name){const t=g(e.arg);t&&i.has(t)&&Le(e)&&Re(o,e,n)}})}function Le(e){const t=g(e.exp);if(null==t)return!1;const n=h(t);return null!=n&&I(n,new Map).known}function Re(e,t,n){const o=t.loc;let r=o.start.offset;for(;r>0&&(" "===e[r-1]||"\t"===e[r-1]||"\n"===e[r-1]);)r-=1;n.remove(r,o.end.offset)}const je=(t,n)=>t.startsWith(".")?e.resolve(e.dirname(n),t):null,Oe=e=>c.readFileSync(e,"utf-8");function Ce(t){const n=[];let o;try{o=c.readdirSync(t,{withFileTypes:!0})}catch{return n}for(const r of o){if("node_modules"===r.name||r.name.startsWith("."))continue;const o=e.join(t,r.name);r.isDirectory()?n.push(...Ce(o)):r.isFile()&&r.name.endsWith(".vue")&&n.push(o)}return n}function ze(t={}){const n=t.include??["."];let o=process.cwd(),s={};return{name:"vue-shaker",enforce:"pre",apply:"build",configResolved(e){o=e.root},async buildStart(){const t=n.flatMap(t=>Ce(e.resolve(o,t)));if(0===t.length)return;const{models:i,plans:c}=await T(t,je,Oe);s=function(e,t){const n=new Map,o=new Map;for(const s of e.values()){const e=new r(s.code);n.set(s.id,e);const i=t.get(s.id);o.set(s.id,i.bail?new Set:xe(s,i.constFold,i.narrow,i,e))}for(const t of e.values())De(t,o,n.get(t.id));const s={};for(const t of e.values())s[t.id]=n.get(t.id).toString();return s}(i,c)},transform(t,n){if(n.includes("?"))return null;const o=n.split("?",1)[0];if(!o.endsWith(".vue"))return null;const r=s[o]??s[e.normalize(o)];return null==r||r===t?null:{code:r,map:null}}}}export{ze as shaker};
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vue-shaker",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Tree shaking for Vue components",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "baseballyama",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/baseballyama/vue-shaker.git",
|
|
10
|
+
"directory": "packages/vue-shaker"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/baseballyama/vue-shaker#readme",
|
|
13
|
+
"bugs": "https://github.com/baseballyama/vue-shaker/issues",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"vue",
|
|
16
|
+
"vue3",
|
|
17
|
+
"tree-shaking",
|
|
18
|
+
"tree-shaker",
|
|
19
|
+
"dead-code-elimination",
|
|
20
|
+
"sfc",
|
|
21
|
+
"vite-plugin",
|
|
22
|
+
"rollup-plugin",
|
|
23
|
+
"optimization",
|
|
24
|
+
"defineProps"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"CHANGELOG.md"
|
|
30
|
+
],
|
|
31
|
+
"main": "./dist/index.js",
|
|
32
|
+
"types": "./dist/types/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/types/index.d.ts",
|
|
36
|
+
"import": "./dist/index.js",
|
|
37
|
+
"require": "./dist/index.cjs"
|
|
38
|
+
},
|
|
39
|
+
"./vite": {
|
|
40
|
+
"types": "./dist/types/vite.d.ts",
|
|
41
|
+
"import": "./dist/vite.js"
|
|
42
|
+
},
|
|
43
|
+
"./node": {
|
|
44
|
+
"types": "./dist/types/scan.d.ts",
|
|
45
|
+
"import": "./dist/scan.js"
|
|
46
|
+
},
|
|
47
|
+
"./package.json": "./package.json"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "rollup -c",
|
|
51
|
+
"build:wasm": "wasm-pack build engine-rs --target nodejs --release --out-dir pkg",
|
|
52
|
+
"prepack": "rollup -c",
|
|
53
|
+
"dev": "rollup -cw",
|
|
54
|
+
"test:watch": "vitest",
|
|
55
|
+
"test": "vitest run"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@babel/parser": "^7.26.0",
|
|
59
|
+
"@vue/compiler-dom": "^3.5.13",
|
|
60
|
+
"@vue/compiler-sfc": "^3.5.13",
|
|
61
|
+
"magic-string": "0.30.10",
|
|
62
|
+
"postcss": "^8.4.49",
|
|
63
|
+
"postcss-selector-parser": "^6.1.2"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@rollup/plugin-commonjs": "25.0.8",
|
|
67
|
+
"@rollup/plugin-node-resolve": "15.2.3",
|
|
68
|
+
"@rollup/plugin-terser": "0.4.4",
|
|
69
|
+
"@rollup/plugin-typescript": "11.1.6",
|
|
70
|
+
"@vitejs/plugin-vue": "^5.2.1",
|
|
71
|
+
"@vue/server-renderer": "^3.5.13",
|
|
72
|
+
"rollup": "4.18.0",
|
|
73
|
+
"tslib": "2.6.2",
|
|
74
|
+
"vite": "^5.4.11",
|
|
75
|
+
"vue": "^3.5.13"
|
|
76
|
+
},
|
|
77
|
+
"peerDependencies": {
|
|
78
|
+
"vue": "^3"
|
|
79
|
+
}
|
|
80
|
+
}
|