vite-plugin-conditional-imports 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +5 -5
- package/dist/index.js +153 -140
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { ConfigEnv, Plugin
|
|
1
|
+
import { ResolvedConfig, ConfigEnv, Plugin } from 'vite';
|
|
2
2
|
|
|
3
3
|
interface ShouldStripContext {
|
|
4
4
|
/** Raw import target as is from source code. */
|
|
5
5
|
target: string;
|
|
6
6
|
/**
|
|
7
|
-
* Resolved import path (relative to project root). Lazy
|
|
8
|
-
*
|
|
7
|
+
* Resolved import path (relative to project root). Lazy, resolution runs only
|
|
8
|
+
* when you access it.
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
resolvedTarget: Promise<string>;
|
|
11
11
|
/** The file where the import is found, relative to project root. */
|
|
12
12
|
source: string;
|
|
13
13
|
/** Import attributes from `with { ... }` clause (e.g. `{ only: 'dev' }`). */
|
|
@@ -37,4 +37,4 @@ interface Options {
|
|
|
37
37
|
}
|
|
38
38
|
declare function conditionalImports(shouldStrip: ShouldStripFn, options?: Options): Plugin;
|
|
39
39
|
|
|
40
|
-
export {
|
|
40
|
+
export { type Options, type ShouldStripContext, type ShouldStripFn, conditionalImports };
|
package/dist/index.js
CHANGED
|
@@ -1,158 +1,196 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { parse, print } from "@swc/core";
|
|
3
|
-
import createDebug from "debug";
|
|
4
2
|
import { unlink } from "fs/promises";
|
|
3
|
+
import { join as join2 } from "path";
|
|
4
|
+
import createDebug from "debug";
|
|
5
|
+
|
|
6
|
+
// src/check.ts
|
|
5
7
|
import { join } from "path";
|
|
8
|
+
import { originalPositionFor, TraceMap } from "@jridgewell/trace-mapping";
|
|
9
|
+
import { Linter } from "eslint";
|
|
10
|
+
|
|
6
11
|
// src/transform.ts
|
|
7
12
|
import path from "path";
|
|
13
|
+
import { parse, print } from "@swc/core";
|
|
8
14
|
import { normalizePath } from "vite";
|
|
9
15
|
|
|
10
16
|
// src/importAttributes.ts
|
|
17
|
+
function getWithObject(node) {
|
|
18
|
+
return node.with ?? node.asserts;
|
|
19
|
+
}
|
|
20
|
+
function getAttrList(node) {
|
|
21
|
+
const withObj = getWithObject(node);
|
|
22
|
+
return withObj?.properties ?? [];
|
|
23
|
+
}
|
|
24
|
+
function getKey(attr) {
|
|
25
|
+
const k = "key" in attr ? attr.key : void 0;
|
|
26
|
+
return k && typeof k === "object" && "value" in k ? k.value : void 0;
|
|
27
|
+
}
|
|
28
|
+
function getVal(attr) {
|
|
29
|
+
const v = "value" in attr ? attr.value : void 0;
|
|
30
|
+
return v && typeof v === "object" && "value" in v ? v.value : void 0;
|
|
31
|
+
}
|
|
11
32
|
function getImportAttributes(node) {
|
|
12
|
-
const list =
|
|
33
|
+
const list = getAttrList(node);
|
|
13
34
|
const out = {};
|
|
14
|
-
if (!list) return out;
|
|
15
35
|
for (const attr of list) {
|
|
16
|
-
const key = attr
|
|
17
|
-
const val = attr
|
|
18
|
-
if (key != null && val != null)
|
|
36
|
+
const key = getKey(attr);
|
|
37
|
+
const val = getVal(attr);
|
|
38
|
+
if (key != null && val != null) {
|
|
39
|
+
out[key] = val;
|
|
40
|
+
}
|
|
19
41
|
}
|
|
20
42
|
return out;
|
|
21
43
|
}
|
|
22
44
|
|
|
23
45
|
// src/transform.ts
|
|
24
|
-
function
|
|
25
|
-
if (!path.isAbsolute(filePath)) return filePath;
|
|
46
|
+
function relativeToRoot(filePath, root) {
|
|
26
47
|
const rel = path.relative(root, filePath);
|
|
27
48
|
if (rel.startsWith("..") || path.isAbsolute(rel)) return filePath;
|
|
28
49
|
return normalizePath(rel);
|
|
29
50
|
}
|
|
30
|
-
function getLocalName(spec) {
|
|
31
|
-
const local = spec.local;
|
|
32
|
-
if (!local) return void 0;
|
|
33
|
-
return "value" in local ? local.value : void 0;
|
|
34
|
-
}
|
|
35
51
|
async function transform(code, id, shouldStrip, context) {
|
|
36
52
|
const mod = await parse(code, {
|
|
37
53
|
syntax: "typescript",
|
|
38
54
|
tsx: id.endsWith(".tsx")
|
|
39
55
|
});
|
|
40
|
-
const strippedImports = [];
|
|
41
|
-
const bindingToSourceFiles = /* @__PURE__ */ new Map();
|
|
42
56
|
const resolveCache = /* @__PURE__ */ new Map();
|
|
57
|
+
const strippedImportTargets = /* @__PURE__ */ new Set();
|
|
58
|
+
const strippedImportSymbols = /* @__PURE__ */ new Set();
|
|
59
|
+
const source = relativeToRoot(id, context.config.root);
|
|
43
60
|
const body = [];
|
|
44
61
|
for (const item of mod.body) {
|
|
45
|
-
if (item.type !== "ImportDeclaration") {
|
|
62
|
+
if (item.type !== "ImportDeclaration" || item.typeOnly) {
|
|
46
63
|
body.push(item);
|
|
47
64
|
continue;
|
|
48
65
|
}
|
|
49
66
|
const target = item.source.value;
|
|
50
|
-
const withObject = getImportAttributes(
|
|
51
|
-
item
|
|
52
|
-
);
|
|
67
|
+
const withObject = getImportAttributes(item);
|
|
53
68
|
const ctx = {
|
|
54
69
|
target,
|
|
55
70
|
get resolvedTarget() {
|
|
56
|
-
let
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
(r) => toRootRelative(r?.id ?? target, context.config.root)
|
|
60
|
-
);
|
|
61
|
-
resolveCache.set(target, p);
|
|
71
|
+
let promise2 = resolveCache.get(target);
|
|
72
|
+
if (promise2) {
|
|
73
|
+
return promise2;
|
|
62
74
|
}
|
|
63
|
-
|
|
75
|
+
promise2 = context.resolve(target, id).then((r) => relativeToRoot(r?.id ?? target, context.config.root));
|
|
76
|
+
resolveCache.set(target, promise2);
|
|
77
|
+
return promise2;
|
|
64
78
|
},
|
|
65
|
-
source
|
|
79
|
+
source,
|
|
66
80
|
withObject,
|
|
67
81
|
config: context.config,
|
|
68
82
|
env: context.env
|
|
69
83
|
};
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (!typeOnly && item.specifiers) {
|
|
84
|
+
const promise = Promise.resolve(shouldStrip(ctx)).then((strip) => {
|
|
85
|
+
if (!strip) {
|
|
86
|
+
return item;
|
|
87
|
+
}
|
|
88
|
+
strippedImportTargets.add(target);
|
|
76
89
|
for (const spec of item.specifiers) {
|
|
77
|
-
|
|
78
|
-
spec
|
|
79
|
-
);
|
|
80
|
-
if (name) {
|
|
81
|
-
strippedImports.push(name);
|
|
82
|
-
const list = bindingToSourceFiles.get(name) ?? [];
|
|
83
|
-
list.push(id);
|
|
84
|
-
bindingToSourceFiles.set(name, list);
|
|
85
|
-
}
|
|
90
|
+
strippedImportSymbols.add(spec.local.value);
|
|
86
91
|
}
|
|
87
|
-
|
|
92
|
+
return null;
|
|
93
|
+
});
|
|
94
|
+
body.push(promise);
|
|
95
|
+
}
|
|
96
|
+
const results = await Promise.all(body);
|
|
97
|
+
mod.body = results.filter((item) => item != null);
|
|
98
|
+
if (strippedImportSymbols.size === 0) {
|
|
99
|
+
return null;
|
|
88
100
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
101
|
+
const out = await print(mod, {
|
|
102
|
+
sourceMaps: true,
|
|
103
|
+
filename: source
|
|
104
|
+
});
|
|
92
105
|
return {
|
|
93
106
|
code: out.code,
|
|
94
107
|
map: out.map ?? null,
|
|
95
|
-
|
|
96
|
-
|
|
108
|
+
strippedImportTargets,
|
|
109
|
+
strippedImportSymbols,
|
|
110
|
+
relativeSource: source
|
|
97
111
|
};
|
|
98
112
|
}
|
|
99
113
|
|
|
100
114
|
// src/check.ts
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
{
|
|
109
|
-
languageOptions: {
|
|
110
|
-
ecmaVersion: "latest",
|
|
111
|
-
sourceType: "module"
|
|
112
|
-
},
|
|
113
|
-
rules: { "no-undef": ["error"] }
|
|
114
|
-
}
|
|
115
|
-
];
|
|
116
|
-
let messages;
|
|
117
|
-
try {
|
|
118
|
-
messages = linter.verify(code, config, "chunk.js");
|
|
119
|
-
} catch {
|
|
120
|
-
return [];
|
|
115
|
+
var eslintConfig = [
|
|
116
|
+
{
|
|
117
|
+
languageOptions: {
|
|
118
|
+
ecmaVersion: "latest",
|
|
119
|
+
sourceType: "module"
|
|
120
|
+
},
|
|
121
|
+
rules: { "no-undef": ["error"] }
|
|
121
122
|
}
|
|
123
|
+
];
|
|
124
|
+
function findUndefinedRefs(code) {
|
|
125
|
+
const linter = new Linter({ configType: "flat" });
|
|
126
|
+
const messages = linter.verify(code, eslintConfig, "chunk.js");
|
|
122
127
|
const seen = /* @__PURE__ */ new Set();
|
|
123
|
-
const
|
|
128
|
+
const refs = [];
|
|
124
129
|
for (const msg of messages) {
|
|
125
130
|
if (msg.ruleId !== "no-undef") continue;
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
const name = msg.message.split("'")[1] ?? null;
|
|
132
|
+
if (!name || seen.has(name)) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
129
135
|
seen.add(name);
|
|
130
|
-
|
|
131
|
-
errors.push({
|
|
136
|
+
refs.push({
|
|
132
137
|
name,
|
|
133
|
-
sourceIds,
|
|
134
138
|
line: msg.line,
|
|
135
139
|
column: msg.column
|
|
136
140
|
});
|
|
137
141
|
}
|
|
138
|
-
return
|
|
142
|
+
return refs;
|
|
139
143
|
}
|
|
140
|
-
function
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
144
|
+
function checkChunk(chunk, importSymbolsToSource, root, outputDir) {
|
|
145
|
+
const errors = [];
|
|
146
|
+
const undefinedRefs = findUndefinedRefs(chunk.code);
|
|
147
|
+
if (undefinedRefs.length === 0) {
|
|
148
|
+
return errors;
|
|
149
|
+
}
|
|
150
|
+
const moduleIdsInChunk = new Set(
|
|
151
|
+
chunk.moduleIds ?? Object.keys(chunk.modules ?? {})
|
|
152
|
+
);
|
|
153
|
+
let traceMap = null;
|
|
154
|
+
for (const undefinedRef of undefinedRefs) {
|
|
155
|
+
const sourceIds = importSymbolsToSource.get(undefinedRef.name);
|
|
156
|
+
if (!sourceIds) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const relevantSourceIds = sourceIds.filter((id) => moduleIdsInChunk.has(id));
|
|
160
|
+
if (relevantSourceIds.length === 0) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
let sourceOfError = null;
|
|
164
|
+
if (chunk.map && undefinedRef.line != null && undefinedRef.column != null) {
|
|
165
|
+
const mapUrl = outputDir ? join(outputDir, chunk.sourcemapFileName ?? `${chunk.fileName}.map`) : null;
|
|
166
|
+
traceMap ??= new TraceMap(chunk.map, mapUrl);
|
|
167
|
+
sourceOfError = originalPositionFor(traceMap, {
|
|
168
|
+
line: undefinedRef.line,
|
|
169
|
+
column: undefinedRef.column
|
|
170
|
+
}).source;
|
|
171
|
+
}
|
|
172
|
+
const errorSourceIds = sourceOfError ? [sourceOfError] : relevantSourceIds;
|
|
173
|
+
const relativeSourceIds = outputDir ? errorSourceIds.map((id) => relativeToRoot(id, root)) : errorSourceIds;
|
|
174
|
+
errors.push(
|
|
175
|
+
`Stripped conditional import binding '${undefinedRef.name}' still in output (${relativeSourceIds.sort().join(", ")})`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
return errors;
|
|
145
179
|
}
|
|
146
|
-
function
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
180
|
+
function checkBundle(bundle, importSymbolsToSource, root, outputDir) {
|
|
181
|
+
if (importSymbolsToSource.size === 0) {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
const errors = [];
|
|
185
|
+
for (const chunkOrAsset of Object.values(bundle)) {
|
|
186
|
+
if (chunkOrAsset.type !== "chunk") {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
errors.push(
|
|
190
|
+
...checkChunk(chunkOrAsset, importSymbolsToSource, root, outputDir)
|
|
151
191
|
);
|
|
152
|
-
return traced.source ?? null;
|
|
153
|
-
} catch {
|
|
154
|
-
return null;
|
|
155
192
|
}
|
|
193
|
+
return errors;
|
|
156
194
|
}
|
|
157
195
|
|
|
158
196
|
// src/index.ts
|
|
@@ -161,8 +199,7 @@ function conditionalImports(shouldStrip, options) {
|
|
|
161
199
|
const autoSourceMaps = options?.autoSourceMaps !== false;
|
|
162
200
|
const applyInServe = options?.applyInServe === true;
|
|
163
201
|
let pluginRequestedSourcemap = false;
|
|
164
|
-
const
|
|
165
|
-
const bindingToSourceFiles = /* @__PURE__ */ new Map();
|
|
202
|
+
const importSymbolsToSource = /* @__PURE__ */ new Map();
|
|
166
203
|
let cachedConfig;
|
|
167
204
|
let cachedEnv;
|
|
168
205
|
return {
|
|
@@ -187,12 +224,14 @@ function conditionalImports(shouldStrip, options) {
|
|
|
187
224
|
cachedConfig = config;
|
|
188
225
|
},
|
|
189
226
|
buildStart() {
|
|
190
|
-
|
|
191
|
-
bindingToSourceFiles.clear();
|
|
227
|
+
importSymbolsToSource.clear();
|
|
192
228
|
},
|
|
193
229
|
async transform(code, id) {
|
|
230
|
+
if (id.includes("/node_modules/")) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
194
233
|
if (!cachedConfig || !cachedEnv) {
|
|
195
|
-
this.error(
|
|
234
|
+
return this.error(
|
|
196
235
|
"vite-plugin-conditional-imports: config or env not resolved"
|
|
197
236
|
);
|
|
198
237
|
}
|
|
@@ -207,61 +246,35 @@ function conditionalImports(shouldStrip, options) {
|
|
|
207
246
|
if (!result) {
|
|
208
247
|
return null;
|
|
209
248
|
}
|
|
210
|
-
|
|
211
|
-
log(
|
|
212
|
-
"stripped import in %s: %s",
|
|
213
|
-
toRootRelative(id, cachedConfig.root),
|
|
214
|
-
result.strippedImports.join(", ")
|
|
215
|
-
);
|
|
249
|
+
for (const target of result.strippedImportTargets) {
|
|
250
|
+
log("stripped import in %s: %s", result.relativeSource, target);
|
|
216
251
|
}
|
|
217
|
-
for (const name of result.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
for (const fid of fromResult) {
|
|
222
|
-
if (!list.includes(fid)) list.push(fid);
|
|
223
|
-
}
|
|
224
|
-
bindingToSourceFiles.set(name, list);
|
|
252
|
+
for (const name of result.strippedImportSymbols) {
|
|
253
|
+
const list = importSymbolsToSource.get(name) ?? [];
|
|
254
|
+
list.push(id);
|
|
255
|
+
importSymbolsToSource.set(name, list);
|
|
225
256
|
}
|
|
226
257
|
return { code: result.code, map: result.map };
|
|
227
258
|
},
|
|
228
|
-
generateBundle(
|
|
229
|
-
if (
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
);
|
|
241
|
-
const chunkMap = chunk.map;
|
|
242
|
-
for (const { name, sourceIds, line, column } of undefinedRefs) {
|
|
243
|
-
let toShow = sourceIds.filter(
|
|
244
|
-
(id) => moduleIdsInChunk.has(id)
|
|
245
|
-
);
|
|
246
|
-
if (toShow.length === 0) toShow = sourceIds;
|
|
247
|
-
let resolvedSingle = null;
|
|
248
|
-
if (chunkMap && line != null && column != null) {
|
|
249
|
-
resolvedSingle = traceToSource(chunkMap, line, column);
|
|
250
|
-
if (resolvedSingle != null)
|
|
251
|
-
resolvedSingle = toShortPath(resolvedSingle);
|
|
252
|
-
}
|
|
253
|
-
const origin = resolvedSingle ? ` (in: ${resolvedSingle})` : toShow.length > 0 ? ` (in: ${toShow.map(toShortPath).join(", ")})` : "";
|
|
254
|
-
this.error(
|
|
255
|
-
`Stripped conditional import binding "${name}" still in output${origin}. Guard or remove the usage so it's tree-shaken.`
|
|
256
|
-
);
|
|
257
|
-
}
|
|
259
|
+
generateBundle(options2, bundle) {
|
|
260
|
+
if (!cachedConfig) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const errors = checkBundle(
|
|
264
|
+
bundle,
|
|
265
|
+
importSymbolsToSource,
|
|
266
|
+
cachedConfig.root,
|
|
267
|
+
options2.dir
|
|
268
|
+
);
|
|
269
|
+
if (errors.length > 0) {
|
|
270
|
+
this.error(errors.join("\n"));
|
|
258
271
|
}
|
|
259
272
|
},
|
|
260
273
|
async writeBundle(options2, bundle) {
|
|
261
274
|
if (!pluginRequestedSourcemap || !options2.dir) return;
|
|
262
275
|
for (const name of Object.keys(bundle)) {
|
|
263
276
|
if (name.endsWith(".map")) {
|
|
264
|
-
await unlink(
|
|
277
|
+
await unlink(join2(options2.dir, name)).catch(() => {
|
|
265
278
|
});
|
|
266
279
|
}
|
|
267
280
|
}
|
package/package.json
CHANGED