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 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
@@ -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"]}
@@ -0,0 +1,5 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ declare const keywordsPlugin: () => Plugin;
4
+
5
+ export { keywordsPlugin as default, keywordsPlugin };
@@ -0,0 +1,5 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ declare const keywordsPlugin: () => Plugin;
4
+
5
+ export { keywordsPlugin as default, keywordsPlugin };
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
+ }