vite-plugin-keywords 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +194 -0
- package/dist/cli.js +21 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +113 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +87 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 cueaz
|
|
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,194 @@
|
|
|
1
|
+
# vite-plugin-keywords
|
|
2
|
+
|
|
3
|
+
[![NPM][npm-badge]][npm-url]
|
|
4
|
+
[![Github CI][ci-badge]][ci-url]
|
|
5
|
+
[![MIT licensed][license-badge]][license-url]
|
|
6
|
+
|
|
7
|
+
[npm-badge]: https://img.shields.io/npm/v/vite-plugin-keywords.svg
|
|
8
|
+
[npm-url]: https://www.npmjs.com/package/vite-plugin-keywords
|
|
9
|
+
[ci-badge]: https://github.com/cueaz/vite-plugin-keywords/actions/workflows/ci.yaml/badge.svg
|
|
10
|
+
[ci-url]: https://github.com/cueaz/vite-plugin-keywords/actions/workflows/ci.yaml
|
|
11
|
+
[license-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
|
12
|
+
[license-url]: https://github.com/cueaz/vite-plugine-keywords/blob/main/LICENSE
|
|
13
|
+
|
|
14
|
+
> [!NOTE]
|
|
15
|
+
> A Rollup version of this plugin, `rollup-plugin-keywords`, is also available. The primary difference is that the Vite plugin utilizes the `hotUpdate` hook to incrementally collect keywords and update modules and types during development. While this documentation is written primarily for the Vite plugin, the setup is almost identical—just add `rollup-plugin-keywords` to your Rollup configuration.
|
|
16
|
+
|
|
17
|
+
A Vite plugin that provides a way to use minifiable `Symbols` (keywords) in place of string literals and object keys, offering a potential strategy for aggressive minification.
|
|
18
|
+
|
|
19
|
+
This approach introduces a trade-off between a small reduction in bundle size and an increase in code complexity. It is best suited for ~~applications where every byte counts~~ minification nerds.
|
|
20
|
+
|
|
21
|
+
## Rationale
|
|
22
|
+
|
|
23
|
+
A common pattern in JavaScript applications, particularly in state management, involves using string literals as unique identifiers.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
// A typical action in a state management system
|
|
27
|
+
function setUser(name: string) {
|
|
28
|
+
return {
|
|
29
|
+
type: 'SET_USER',
|
|
30
|
+
payload: { name },
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
While minifiers can shorten variable and function names, they cannot alter the string literals. When an application defines dozens of these identifiers, they accumulate as unminifiable overhead in the final bundle.
|
|
36
|
+
|
|
37
|
+
This plugin addresses this by enabling the use of `Symbol` primitives, which, when assigned to variables, can be safely minified.
|
|
38
|
+
|
|
39
|
+
**Standard Approach:**
|
|
40
|
+
|
|
41
|
+
In this standard pattern, both the property key `type` and `payload`, and the type value `'SET_USER'` are strings that resist minification.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
interface SetUserAction {
|
|
45
|
+
type: 'SET_USER';
|
|
46
|
+
payload: { name: string };
|
|
47
|
+
}
|
|
48
|
+
const setUser = (payload: { name: string }): SetUserAction => ({
|
|
49
|
+
type: 'SET_USER',
|
|
50
|
+
payload,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
function reducer(state: any, action: SetUserAction) {
|
|
54
|
+
if (action.type === 'SET_USER') {
|
|
55
|
+
// use action.payload
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Minified Output: The strings 'type', 'payload', and 'SET_USER' persist.
|
|
60
|
+
// prettier-ignore
|
|
61
|
+
const a=p=>({type:'SET_USER',payload:p});
|
|
62
|
+
// prettier-ignore
|
|
63
|
+
function b(s,c){if(c.type==='SET_USER'){/*...*/}}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**With `vite-plugin-keywords`:**
|
|
67
|
+
|
|
68
|
+
By importing from the `virtual:keywords` module, you can replace internal, structural strings with minifiable `Symbol` variables, while leaving the data model intact.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import * as K from 'virtual:keywords';
|
|
72
|
+
|
|
73
|
+
interface SetUserAction {
|
|
74
|
+
[K.type]: typeof K.SET_USER;
|
|
75
|
+
// The payload's structure remains unchanged for compatibility with external data sources.
|
|
76
|
+
[K.payload]: { name: string };
|
|
77
|
+
}
|
|
78
|
+
const setUser = (payload: { name: string }): SetUserAction => ({
|
|
79
|
+
[K.type]: K.SET_USER,
|
|
80
|
+
[K.payload]: payload,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
function reducer(state: any, action: SetUserAction) {
|
|
84
|
+
if (action[K.type] === K.SET_USER) {
|
|
85
|
+
// use action[K.payload]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Minified Output: All structural identifiers become single-character variables.
|
|
90
|
+
// prettier-ignore
|
|
91
|
+
const k=Symbol,a=k(),b=k(),c=k(),d=p=>({[a]:b,[c]:p});
|
|
92
|
+
// prettier-ignore
|
|
93
|
+
function e(s,f){if(f[a]===b){/*...*/}}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This transforms static string overhead into minifiable variables, allowing the minifier to do what it does best.
|
|
97
|
+
|
|
98
|
+
## Comparison to Property Mangling
|
|
99
|
+
|
|
100
|
+
Property mangling lacks the semantic context to know which keys are safe to alter, often relying on fragile regex or naming conventions. This plugin takes a different approach by operating on explicit developer intent. Rather than asking a minifier to guess, you refactor a string literal into a minifiable variable (`K.myKeyword`) that holds a unique `Symbol`. This provides an unambiguous, structural hint to the build process, enabling safe and predictable minification of identifiers without the risks associated with global property renaming.
|
|
101
|
+
|
|
102
|
+
## How It Works
|
|
103
|
+
|
|
104
|
+
The plugin works by scanning your code for usages of the `virtual:keywords` module and generating the corresponding `Symbol` exports and types on the fly.
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
// virtual:keywords
|
|
108
|
+
const __SYMBOL__ = Symbol;
|
|
109
|
+
const _type = /* @__PURE__ */ __SYMBOL__(); // __SYMBOL__('type') in dev mode
|
|
110
|
+
const _payload = /* @__PURE__ */ __SYMBOL__(); // __SYMBOL__('payload') in dev mode
|
|
111
|
+
const _SET_USER = /* @__PURE__ */ __SYMBOL__(); // __SYMBOL__('SET_USER') in dev mode
|
|
112
|
+
// ... and so on for all other keywords found.
|
|
113
|
+
export {
|
|
114
|
+
_type as type,
|
|
115
|
+
_payload as payload,
|
|
116
|
+
_SET_USER as SET_USER,
|
|
117
|
+
// ... and so on
|
|
118
|
+
};
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Installation
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
npm install -D vite-plugin-keywords
|
|
125
|
+
# or
|
|
126
|
+
yarn add -D vite-plugin-keywords
|
|
127
|
+
# or
|
|
128
|
+
pnpm add -D vite-plugin-keywords
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Setup
|
|
132
|
+
|
|
133
|
+
1. Add the plugin to your `vite.config.ts`.
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
// vite.config.ts
|
|
137
|
+
import { defineConfig } from 'vite';
|
|
138
|
+
import keywords from 'vite-plugin-keywords';
|
|
139
|
+
|
|
140
|
+
export default defineConfig({
|
|
141
|
+
plugins: [keywords()],
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
2. Include the generated types file in your `tsconfig.json` or `src/env.d.ts`.
|
|
146
|
+
|
|
147
|
+
```jsonc
|
|
148
|
+
// tsconfig.json
|
|
149
|
+
{
|
|
150
|
+
// ...
|
|
151
|
+
"include": [
|
|
152
|
+
"src",
|
|
153
|
+
".keywords/types.d.ts", // Add this line
|
|
154
|
+
],
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
// src/env.d.ts
|
|
160
|
+
/// <reference path="../.keywords/types.d.ts" />
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
3. Exclude the generated types file from your version control system (e.g., Git).
|
|
164
|
+
|
|
165
|
+
```gitignore
|
|
166
|
+
# .gitignore
|
|
167
|
+
.keywords/
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
4. Ensure that your type-checking script in `package.json` is updated to run the plugin first:
|
|
171
|
+
|
|
172
|
+
```jsonc
|
|
173
|
+
// package.json
|
|
174
|
+
{
|
|
175
|
+
"scripts": {
|
|
176
|
+
"typecheck": "keywords && tsc --noEmit",
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
5. The `.keywords/types.d.ts` type file is created automatically on `vite dev/build`, or manually via the `keywords` script.
|
|
182
|
+
|
|
183
|
+
## Options
|
|
184
|
+
|
|
185
|
+
None
|
|
186
|
+
|
|
187
|
+
## Limitations
|
|
188
|
+
|
|
189
|
+
- **(TODO) Frameworks**: The plugin uses Babel to parse JavaScript and TypeScript files. It cannot parse keywords from Vue, Svelte, or Astro files yet.
|
|
190
|
+
- **Dynamic Access**: Only static property access (e.g., `K.myKeyword`) is detected. Dynamic, computed access (e.g., `K['myKeyword']`) will not be identified by the plugin.
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
MIT
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/cli.ts
|
|
2
|
+
import {
|
|
3
|
+
collectKeywordsAndGenerateTypes,
|
|
4
|
+
createPrefixedLogger
|
|
5
|
+
} from "minifiable-keywords";
|
|
6
|
+
import { resolveConfig } from "vite";
|
|
7
|
+
|
|
8
|
+
// src/shared.ts
|
|
9
|
+
var PLUGIN_NAME = "vite-plugin-keywords";
|
|
10
|
+
|
|
11
|
+
// src/cli.ts
|
|
12
|
+
var main = async () => {
|
|
13
|
+
const config = await resolveConfig({}, "build");
|
|
14
|
+
const logger = createPrefixedLogger(config.logger, PLUGIN_NAME);
|
|
15
|
+
await collectKeywordsAndGenerateTypes(config.root, logger, [
|
|
16
|
+
config.build.outDir,
|
|
17
|
+
config.cacheDir
|
|
18
|
+
]);
|
|
19
|
+
};
|
|
20
|
+
await main();
|
|
21
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/shared.ts"],"sourcesContent":["import {\n collectKeywordsAndGenerateTypes,\n createPrefixedLogger,\n} from 'minifiable-keywords';\nimport { resolveConfig } from 'vite';\nimport { PLUGIN_NAME } from './shared';\n\nconst main = async () => {\n const config = await resolveConfig({}, 'build');\n const logger = createPrefixedLogger(config.logger, PLUGIN_NAME);\n\n // const keywordsPlugin = config.plugins.find(\n // (plugin) => plugin.name === PLUGIN_NAME,\n // );\n // if (!keywordsPlugin) {\n // logger.error('Keywords plugin not found in Vite configuration.');\n // process.exit(1);\n // }\n\n await collectKeywordsAndGenerateTypes(config.root, logger, [\n config.build.outDir,\n config.cacheDir,\n ]);\n};\n\nawait main();\n","export const PLUGIN_NAME = 'vite-plugin-keywords';\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,qBAAqB;;;ACJvB,IAAM,cAAc;;;ADO3B,IAAM,OAAO,YAAY;AACvB,QAAM,SAAS,MAAM,cAAc,CAAC,GAAG,OAAO;AAC9C,QAAM,SAAS,qBAAqB,OAAO,QAAQ,WAAW;AAU9D,QAAM,gCAAgC,OAAO,MAAM,QAAQ;AAAA,IACzD,OAAO,MAAM;AAAA,IACb,OAAO;AAAA,EACT,CAAC;AACH;AAEA,MAAM,KAAK;","names":[]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
default: () => index_default,
|
|
34
|
+
keywordsPlugin: () => keywordsPlugin
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
38
|
+
var import_minifiable_keywords = require("minifiable-keywords");
|
|
39
|
+
|
|
40
|
+
// src/shared.ts
|
|
41
|
+
var PLUGIN_NAME = "vite-plugin-keywords";
|
|
42
|
+
|
|
43
|
+
// src/index.ts
|
|
44
|
+
var keywordsPlugin = () => {
|
|
45
|
+
let collectedKeywords;
|
|
46
|
+
let config;
|
|
47
|
+
let logger;
|
|
48
|
+
const invalidateModule = (absoluteId, moduleGraph) => {
|
|
49
|
+
const module2 = moduleGraph.getModuleById(absoluteId);
|
|
50
|
+
if (module2) {
|
|
51
|
+
moduleGraph.invalidateModule(module2);
|
|
52
|
+
module2.lastHMRTimestamp = module2.lastInvalidationTimestamp || Date.now();
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
name: PLUGIN_NAME,
|
|
57
|
+
configResolved(resolvedConfig) {
|
|
58
|
+
config = resolvedConfig;
|
|
59
|
+
logger = (0, import_minifiable_keywords.createPrefixedLogger)(config.logger, PLUGIN_NAME);
|
|
60
|
+
},
|
|
61
|
+
async buildStart() {
|
|
62
|
+
collectedKeywords = await (0, import_minifiable_keywords.collectKeywordsAndGenerateTypes)(
|
|
63
|
+
config.root,
|
|
64
|
+
logger,
|
|
65
|
+
[config.build.outDir, config.cacheDir]
|
|
66
|
+
);
|
|
67
|
+
},
|
|
68
|
+
resolveId(source, importer) {
|
|
69
|
+
if (!importer) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const [validSource] = (0, import_minifiable_keywords.splitQuery)(source);
|
|
73
|
+
if (validSource === import_minifiable_keywords.VIRTUAL_MODULE_ID) {
|
|
74
|
+
return import_minifiable_keywords.RESOLVED_VIRTUAL_MODULE_ID;
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
load(id) {
|
|
78
|
+
const [validId] = (0, import_minifiable_keywords.splitQuery)(id);
|
|
79
|
+
if (validId === import_minifiable_keywords.RESOLVED_VIRTUAL_MODULE_ID) {
|
|
80
|
+
const isDev = config.mode === "development";
|
|
81
|
+
return (0, import_minifiable_keywords.generateModuleCode)(collectedKeywords, isDev);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
async hotUpdate({ type, file, read }) {
|
|
85
|
+
if (type === "delete") return;
|
|
86
|
+
const fileExt = import_node_path.default.extname(file);
|
|
87
|
+
if (![".js", ".ts", ".jsx", ".tsx"].includes(fileExt) || file.includes("/.")) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const code = await read();
|
|
91
|
+
const keywordsInFile = (0, import_minifiable_keywords.extractKeywords)(code);
|
|
92
|
+
if (keywordsInFile.size === 0) return;
|
|
93
|
+
const initialSize = collectedKeywords.size;
|
|
94
|
+
for (const key of keywordsInFile) {
|
|
95
|
+
collectedKeywords.add(key);
|
|
96
|
+
}
|
|
97
|
+
const newKeywordsAdded = collectedKeywords.size > initialSize;
|
|
98
|
+
if (newKeywordsAdded) {
|
|
99
|
+
invalidateModule(
|
|
100
|
+
import_minifiable_keywords.RESOLVED_VIRTUAL_MODULE_ID,
|
|
101
|
+
this.environment.moduleGraph
|
|
102
|
+
);
|
|
103
|
+
await (0, import_minifiable_keywords.generateTypesFile)(collectedKeywords, config.root);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
var index_default = keywordsPlugin;
|
|
109
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
110
|
+
0 && (module.exports = {
|
|
111
|
+
keywordsPlugin
|
|
112
|
+
});
|
|
113
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/shared.ts"],"sourcesContent":["import path from 'node:path';\nimport {\n collectKeywordsAndGenerateTypes,\n createPrefixedLogger,\n extractKeywords,\n generateModuleCode,\n generateTypesFile,\n RESOLVED_VIRTUAL_MODULE_ID,\n splitQuery,\n VIRTUAL_MODULE_ID,\n type PrefixedLogger,\n} from 'minifiable-keywords';\nimport type { EnvironmentModuleGraph, Plugin, ResolvedConfig } from 'vite';\nimport { PLUGIN_NAME } from './shared';\n\nexport const keywordsPlugin = (): Plugin => {\n let collectedKeywords: Set<string>;\n let config: ResolvedConfig;\n let logger: PrefixedLogger;\n\n const invalidateModule = (\n absoluteId: string,\n moduleGraph: EnvironmentModuleGraph,\n ): void => {\n const module = moduleGraph.getModuleById(absoluteId);\n if (module) {\n moduleGraph.invalidateModule(module);\n module.lastHMRTimestamp = module.lastInvalidationTimestamp || Date.now();\n }\n };\n\n return {\n name: PLUGIN_NAME,\n\n configResolved(resolvedConfig) {\n config = resolvedConfig;\n logger = createPrefixedLogger(config.logger, PLUGIN_NAME);\n },\n\n async buildStart() {\n collectedKeywords = await collectKeywordsAndGenerateTypes(\n config.root,\n logger,\n [config.build.outDir, config.cacheDir],\n );\n },\n\n resolveId(source, importer) {\n if (!importer) {\n return;\n }\n const [validSource] = splitQuery(source);\n if (validSource === VIRTUAL_MODULE_ID) {\n return RESOLVED_VIRTUAL_MODULE_ID;\n }\n },\n\n load(id) {\n const [validId] = splitQuery(id);\n if (validId === RESOLVED_VIRTUAL_MODULE_ID) {\n const isDev = config.mode === 'development';\n return generateModuleCode(collectedKeywords, isDev);\n }\n },\n\n async hotUpdate({ type, file, read }) {\n if (type === 'delete') return;\n\n const fileExt = path.extname(file);\n if (\n !['.js', '.ts', '.jsx', '.tsx'].includes(fileExt) ||\n file.includes('/.')\n ) {\n return;\n }\n\n const code = await read();\n const keywordsInFile = extractKeywords(code);\n if (keywordsInFile.size === 0) return;\n\n const initialSize = collectedKeywords.size;\n for (const key of keywordsInFile) {\n collectedKeywords.add(key);\n }\n\n const newKeywordsAdded = collectedKeywords.size > initialSize;\n if (newKeywordsAdded) {\n invalidateModule(\n RESOLVED_VIRTUAL_MODULE_ID,\n this.environment.moduleGraph,\n );\n await generateTypesFile(collectedKeywords, config.root);\n }\n },\n };\n};\n\nexport default keywordsPlugin;\n","export const PLUGIN_NAME = 'vite-plugin-keywords';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAAiB;AACjB,iCAUO;;;ACXA,IAAM,cAAc;;;ADepB,IAAM,iBAAiB,MAAc;AAC1C,MAAI;AACJ,MAAI;AACJ,MAAI;AAEJ,QAAM,mBAAmB,CACvB,YACA,gBACS;AACT,UAAMA,UAAS,YAAY,cAAc,UAAU;AACnD,QAAIA,SAAQ;AACV,kBAAY,iBAAiBA,OAAM;AACnC,MAAAA,QAAO,mBAAmBA,QAAO,6BAA6B,KAAK,IAAI;AAAA,IACzE;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,eAAe,gBAAgB;AAC7B,eAAS;AACT,mBAAS,iDAAqB,OAAO,QAAQ,WAAW;AAAA,IAC1D;AAAA,IAEA,MAAM,aAAa;AACjB,0BAAoB,UAAM;AAAA,QACxB,OAAO;AAAA,QACP;AAAA,QACA,CAAC,OAAO,MAAM,QAAQ,OAAO,QAAQ;AAAA,MACvC;AAAA,IACF;AAAA,IAEA,UAAU,QAAQ,UAAU;AAC1B,UAAI,CAAC,UAAU;AACb;AAAA,MACF;AACA,YAAM,CAAC,WAAW,QAAI,uCAAW,MAAM;AACvC,UAAI,gBAAgB,8CAAmB;AACrC,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,KAAK,IAAI;AACP,YAAM,CAAC,OAAO,QAAI,uCAAW,EAAE;AAC/B,UAAI,YAAY,uDAA4B;AAC1C,cAAM,QAAQ,OAAO,SAAS;AAC9B,mBAAO,+CAAmB,mBAAmB,KAAK;AAAA,MACpD;AAAA,IACF;AAAA,IAEA,MAAM,UAAU,EAAE,MAAM,MAAM,KAAK,GAAG;AACpC,UAAI,SAAS,SAAU;AAEvB,YAAM,UAAU,iBAAAC,QAAK,QAAQ,IAAI;AACjC,UACE,CAAC,CAAC,OAAO,OAAO,QAAQ,MAAM,EAAE,SAAS,OAAO,KAChD,KAAK,SAAS,IAAI,GAClB;AACA;AAAA,MACF;AAEA,YAAM,OAAO,MAAM,KAAK;AACxB,YAAM,qBAAiB,4CAAgB,IAAI;AAC3C,UAAI,eAAe,SAAS,EAAG;AAE/B,YAAM,cAAc,kBAAkB;AACtC,iBAAW,OAAO,gBAAgB;AAChC,0BAAkB,IAAI,GAAG;AAAA,MAC3B;AAEA,YAAM,mBAAmB,kBAAkB,OAAO;AAClD,UAAI,kBAAkB;AACpB;AAAA,UACE;AAAA,UACA,KAAK,YAAY;AAAA,QACnB;AACA,kBAAM,8CAAkB,mBAAmB,OAAO,IAAI;AAAA,MACxD;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,gBAAQ;","names":["module","path"]}
|
package/dist/index.d.cts
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import path from "path";
|
|
3
|
+
import {
|
|
4
|
+
collectKeywordsAndGenerateTypes,
|
|
5
|
+
createPrefixedLogger,
|
|
6
|
+
extractKeywords,
|
|
7
|
+
generateModuleCode,
|
|
8
|
+
generateTypesFile,
|
|
9
|
+
RESOLVED_VIRTUAL_MODULE_ID,
|
|
10
|
+
splitQuery,
|
|
11
|
+
VIRTUAL_MODULE_ID
|
|
12
|
+
} from "minifiable-keywords";
|
|
13
|
+
|
|
14
|
+
// src/shared.ts
|
|
15
|
+
var PLUGIN_NAME = "vite-plugin-keywords";
|
|
16
|
+
|
|
17
|
+
// src/index.ts
|
|
18
|
+
var keywordsPlugin = () => {
|
|
19
|
+
let collectedKeywords;
|
|
20
|
+
let config;
|
|
21
|
+
let logger;
|
|
22
|
+
const invalidateModule = (absoluteId, moduleGraph) => {
|
|
23
|
+
const module = moduleGraph.getModuleById(absoluteId);
|
|
24
|
+
if (module) {
|
|
25
|
+
moduleGraph.invalidateModule(module);
|
|
26
|
+
module.lastHMRTimestamp = module.lastInvalidationTimestamp || Date.now();
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
name: PLUGIN_NAME,
|
|
31
|
+
configResolved(resolvedConfig) {
|
|
32
|
+
config = resolvedConfig;
|
|
33
|
+
logger = createPrefixedLogger(config.logger, PLUGIN_NAME);
|
|
34
|
+
},
|
|
35
|
+
async buildStart() {
|
|
36
|
+
collectedKeywords = await collectKeywordsAndGenerateTypes(
|
|
37
|
+
config.root,
|
|
38
|
+
logger,
|
|
39
|
+
[config.build.outDir, config.cacheDir]
|
|
40
|
+
);
|
|
41
|
+
},
|
|
42
|
+
resolveId(source, importer) {
|
|
43
|
+
if (!importer) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const [validSource] = splitQuery(source);
|
|
47
|
+
if (validSource === VIRTUAL_MODULE_ID) {
|
|
48
|
+
return RESOLVED_VIRTUAL_MODULE_ID;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
load(id) {
|
|
52
|
+
const [validId] = splitQuery(id);
|
|
53
|
+
if (validId === RESOLVED_VIRTUAL_MODULE_ID) {
|
|
54
|
+
const isDev = config.mode === "development";
|
|
55
|
+
return generateModuleCode(collectedKeywords, isDev);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
async hotUpdate({ type, file, read }) {
|
|
59
|
+
if (type === "delete") return;
|
|
60
|
+
const fileExt = path.extname(file);
|
|
61
|
+
if (![".js", ".ts", ".jsx", ".tsx"].includes(fileExt) || file.includes("/.")) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const code = await read();
|
|
65
|
+
const keywordsInFile = extractKeywords(code);
|
|
66
|
+
if (keywordsInFile.size === 0) return;
|
|
67
|
+
const initialSize = collectedKeywords.size;
|
|
68
|
+
for (const key of keywordsInFile) {
|
|
69
|
+
collectedKeywords.add(key);
|
|
70
|
+
}
|
|
71
|
+
const newKeywordsAdded = collectedKeywords.size > initialSize;
|
|
72
|
+
if (newKeywordsAdded) {
|
|
73
|
+
invalidateModule(
|
|
74
|
+
RESOLVED_VIRTUAL_MODULE_ID,
|
|
75
|
+
this.environment.moduleGraph
|
|
76
|
+
);
|
|
77
|
+
await generateTypesFile(collectedKeywords, config.root);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
var index_default = keywordsPlugin;
|
|
83
|
+
export {
|
|
84
|
+
index_default as default,
|
|
85
|
+
keywordsPlugin
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/shared.ts"],"sourcesContent":["import path from 'node:path';\nimport {\n collectKeywordsAndGenerateTypes,\n createPrefixedLogger,\n extractKeywords,\n generateModuleCode,\n generateTypesFile,\n RESOLVED_VIRTUAL_MODULE_ID,\n splitQuery,\n VIRTUAL_MODULE_ID,\n type PrefixedLogger,\n} from 'minifiable-keywords';\nimport type { EnvironmentModuleGraph, Plugin, ResolvedConfig } from 'vite';\nimport { PLUGIN_NAME } from './shared';\n\nexport const keywordsPlugin = (): Plugin => {\n let collectedKeywords: Set<string>;\n let config: ResolvedConfig;\n let logger: PrefixedLogger;\n\n const invalidateModule = (\n absoluteId: string,\n moduleGraph: EnvironmentModuleGraph,\n ): void => {\n const module = moduleGraph.getModuleById(absoluteId);\n if (module) {\n moduleGraph.invalidateModule(module);\n module.lastHMRTimestamp = module.lastInvalidationTimestamp || Date.now();\n }\n };\n\n return {\n name: PLUGIN_NAME,\n\n configResolved(resolvedConfig) {\n config = resolvedConfig;\n logger = createPrefixedLogger(config.logger, PLUGIN_NAME);\n },\n\n async buildStart() {\n collectedKeywords = await collectKeywordsAndGenerateTypes(\n config.root,\n logger,\n [config.build.outDir, config.cacheDir],\n );\n },\n\n resolveId(source, importer) {\n if (!importer) {\n return;\n }\n const [validSource] = splitQuery(source);\n if (validSource === VIRTUAL_MODULE_ID) {\n return RESOLVED_VIRTUAL_MODULE_ID;\n }\n },\n\n load(id) {\n const [validId] = splitQuery(id);\n if (validId === RESOLVED_VIRTUAL_MODULE_ID) {\n const isDev = config.mode === 'development';\n return generateModuleCode(collectedKeywords, isDev);\n }\n },\n\n async hotUpdate({ type, file, read }) {\n if (type === 'delete') return;\n\n const fileExt = path.extname(file);\n if (\n !['.js', '.ts', '.jsx', '.tsx'].includes(fileExt) ||\n file.includes('/.')\n ) {\n return;\n }\n\n const code = await read();\n const keywordsInFile = extractKeywords(code);\n if (keywordsInFile.size === 0) return;\n\n const initialSize = collectedKeywords.size;\n for (const key of keywordsInFile) {\n collectedKeywords.add(key);\n }\n\n const newKeywordsAdded = collectedKeywords.size > initialSize;\n if (newKeywordsAdded) {\n invalidateModule(\n RESOLVED_VIRTUAL_MODULE_ID,\n this.environment.moduleGraph,\n );\n await generateTypesFile(collectedKeywords, config.root);\n }\n },\n };\n};\n\nexport default keywordsPlugin;\n","export const PLUGIN_NAME = 'vite-plugin-keywords';\n"],"mappings":";AAAA,OAAO,UAAU;AACjB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;;;ACXA,IAAM,cAAc;;;ADepB,IAAM,iBAAiB,MAAc;AAC1C,MAAI;AACJ,MAAI;AACJ,MAAI;AAEJ,QAAM,mBAAmB,CACvB,YACA,gBACS;AACT,UAAM,SAAS,YAAY,cAAc,UAAU;AACnD,QAAI,QAAQ;AACV,kBAAY,iBAAiB,MAAM;AACnC,aAAO,mBAAmB,OAAO,6BAA6B,KAAK,IAAI;AAAA,IACzE;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,eAAe,gBAAgB;AAC7B,eAAS;AACT,eAAS,qBAAqB,OAAO,QAAQ,WAAW;AAAA,IAC1D;AAAA,IAEA,MAAM,aAAa;AACjB,0BAAoB,MAAM;AAAA,QACxB,OAAO;AAAA,QACP;AAAA,QACA,CAAC,OAAO,MAAM,QAAQ,OAAO,QAAQ;AAAA,MACvC;AAAA,IACF;AAAA,IAEA,UAAU,QAAQ,UAAU;AAC1B,UAAI,CAAC,UAAU;AACb;AAAA,MACF;AACA,YAAM,CAAC,WAAW,IAAI,WAAW,MAAM;AACvC,UAAI,gBAAgB,mBAAmB;AACrC,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,KAAK,IAAI;AACP,YAAM,CAAC,OAAO,IAAI,WAAW,EAAE;AAC/B,UAAI,YAAY,4BAA4B;AAC1C,cAAM,QAAQ,OAAO,SAAS;AAC9B,eAAO,mBAAmB,mBAAmB,KAAK;AAAA,MACpD;AAAA,IACF;AAAA,IAEA,MAAM,UAAU,EAAE,MAAM,MAAM,KAAK,GAAG;AACpC,UAAI,SAAS,SAAU;AAEvB,YAAM,UAAU,KAAK,QAAQ,IAAI;AACjC,UACE,CAAC,CAAC,OAAO,OAAO,QAAQ,MAAM,EAAE,SAAS,OAAO,KAChD,KAAK,SAAS,IAAI,GAClB;AACA;AAAA,MACF;AAEA,YAAM,OAAO,MAAM,KAAK;AACxB,YAAM,iBAAiB,gBAAgB,IAAI;AAC3C,UAAI,eAAe,SAAS,EAAG;AAE/B,YAAM,cAAc,kBAAkB;AACtC,iBAAW,OAAO,gBAAgB;AAChC,0BAAkB,IAAI,GAAG;AAAA,MAC3B;AAEA,YAAM,mBAAmB,kBAAkB,OAAO;AAClD,UAAI,kBAAkB;AACpB;AAAA,UACE;AAAA,UACA,KAAK,YAAY;AAAA,QACnB;AACA,cAAM,kBAAkB,mBAAmB,OAAO,IAAI;AAAA,MACxD;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,gBAAQ;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vite-plugin-keywords",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A Vite plugin that provides minifiable Symbols (keywords) to use in place of string literals for aggressive minification.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"vite",
|
|
7
|
+
"vite-plugin",
|
|
8
|
+
"minification"
|
|
9
|
+
],
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/cueaz/vite-plugin-keywords.git"
|
|
13
|
+
},
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "cueaz",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"import": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"require": {
|
|
24
|
+
"types": "./dist/index.d.cts",
|
|
25
|
+
"default": "./dist/index.cjs"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"./package.json": "./package.json"
|
|
29
|
+
},
|
|
30
|
+
"main": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"bin": {
|
|
33
|
+
"keywords": "dist/cli.js"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist"
|
|
37
|
+
],
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"minifiable-keywords": "1.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^24.1.0",
|
|
43
|
+
"rimraf": "^6.0.1",
|
|
44
|
+
"tsup": "^8.5.0",
|
|
45
|
+
"vitest": "^3.2.4"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"vite": "^6.0.0 || ^7.0.0"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsup",
|
|
52
|
+
"clean": "rimraf dist",
|
|
53
|
+
"test": "vitest"
|
|
54
|
+
}
|
|
55
|
+
}
|