vite-plugin-keywords 1.2.1 → 1.2.3

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/README.md CHANGED
@@ -196,7 +196,9 @@ npx keywords [--config <path>]
196
196
  - **Type:** `string[]`
197
197
  - **Default:** `[]`
198
198
 
199
- An array of module specifiers (e.g. `'react'` or `'@vue/runtime-core'`) that the plugin should scan for keyword usage in addition to `virtual:keywords`. This is useful if you alias internal module exports that you wish to have extracted as keywords automatically.
199
+ An array of module specifiers that the plugin should scan for keyword usage in addition to `virtual:keywords`.
200
+
201
+ This is particularly useful in a monorepo where keywords are centralized in a shared package (e.g., `@myrepo/keywords` which simply re-exports `virtual:keywords`).
200
202
 
201
203
  ```ts
202
204
  import keywords from 'vite-plugin-keywords';
@@ -204,12 +206,24 @@ import keywords from 'vite-plugin-keywords';
204
206
  export default defineConfig({
205
207
  plugins: [
206
208
  keywords({
207
- additionalModulesToScan: ['react'],
209
+ additionalModulesToScan: ['@myrepo/keywords'], // Allows extracting keywords imported from the shared package
208
210
  }),
209
211
  ],
210
212
  });
211
213
  ```
212
214
 
215
+ ### `isDev`
216
+
217
+ - **Type:** `boolean`
218
+ - **Default:** `process.env.NODE_ENV === 'development'` (for Rollup) or `config.mode === 'development'` (for Vite)
219
+
220
+ Manually overrides the build environment mode.
221
+
222
+ - `true`: Generates `Symbol('keyword')` with string descriptions for easier debugging.
223
+ - `false`: Generates empty `Symbol()` to completely remove string literals from the bundle.
224
+
225
+ This is especially useful in Rollup to output both dev and prod bundles from a single configuration without relying on `process.env.NODE_ENV`.
226
+
213
227
  ## Limitations
214
228
 
215
229
  - **Serialization**: Keywords are compiled down to `Symbol` primitives, which cannot be serialized (e.g., via `JSON.stringify()`). Avoid using keywords for keys in state objects that need to be persisted to LocalStorage, sent over network boundaries, or used in SSR hydration.
package/dist/cli.js CHANGED
@@ -9,9 +9,10 @@ import _traverse from "@babel/traverse";
9
9
  import { globby } from "globby";
10
10
  var VIRTUAL_MODULE_ID = "virtual:keywords";
11
11
  var RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
12
- var buildOptions = (options = {}) => {
12
+ var resolveOptions = (options) => {
13
13
  return {
14
- additionalModulesToScan: options.additionalModulesToScan || []
14
+ additionalModulesToScan: options?.additionalModulesToScan || [],
15
+ isDev: options?.isDev
15
16
  };
16
17
  };
17
18
  var createPrefixedLogger = (logger, pluginName, usePrefix = true) => {
@@ -100,7 +101,8 @@ ${exportDeclaration}
100
101
  await writeFile(path.join(pluginRoot, filename), `${content.trim()}
101
102
  `);
102
103
  };
103
- var collectKeywordsFromFiles = async (root, logger, ignoredDirs = [], options = buildOptions()) => {
104
+ var collectKeywordsFromFiles = async (root, logger, ignoredDirs = [], options) => {
105
+ const resolvedOptions = resolveOptions(options);
104
106
  const collectedKeywords = /* @__PURE__ */ new Set();
105
107
  logger.info("Scanning project files for keywords...");
106
108
  const files = await globby("**/*.{js,ts,jsx,tsx}", {
@@ -118,7 +120,7 @@ var collectKeywordsFromFiles = async (root, logger, ignoredDirs = [], options =
118
120
  const code = await readFile(file, "utf-8");
119
121
  const keywords = extractKeywords(
120
122
  code,
121
- options.additionalModulesToScan
123
+ resolvedOptions.additionalModulesToScan
122
124
  );
123
125
  for (const key of keywords) {
124
126
  collectedKeywords.add(key);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../../minifiable-keywords/src/index.ts","../src/shared.ts"],"sourcesContent":["import { parseArgs } from 'node:util';\nimport {\n collectKeywordsAndGenerateTypes,\n createPrefixedLogger,\n} from 'minifiable-keywords';\nimport { resolveConfig } from 'vite';\nimport { PLUGIN_NAME } from './shared';\n\nconst main = async () => {\n const { values } = parseArgs({\n options: {\n config: {\n type: 'string',\n short: 'c',\n },\n },\n strict: false,\n });\n\n let config;\n try {\n config = await resolveConfig(\n {\n configFile:\n typeof values.config === 'string' ? values.config : undefined,\n },\n 'build',\n );\n } catch (error: any) {\n console.error(\n `[${PLUGIN_NAME}] Failed to resolve Vite config: ${error.message}`,\n );\n process.exit(1);\n }\n\n const logger = createPrefixedLogger(config.logger, PLUGIN_NAME);\n\n const keywordsPlugin = config.plugins.find(\n (plugin) => plugin.name === PLUGIN_NAME,\n );\n\n let pluginOptions;\n if (keywordsPlugin) {\n pluginOptions = keywordsPlugin.api?.options;\n } else {\n logger.error('Keywords plugin not found in Vite configuration.');\n process.exit(1);\n }\n\n try {\n await collectKeywordsAndGenerateTypes(\n config.root,\n logger,\n [config.build.outDir, config.cacheDir],\n pluginOptions,\n );\n } catch (error: any) {\n logger.error(\n `Failed to collect keywords and generate types: ${error.message}`,\n );\n process.exit(1);\n }\n};\n\nawait main();\n","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { parse } from '@babel/parser';\nimport _traverse, { type Node } from '@babel/traverse';\nimport { globby } from 'globby';\n\nexport const VIRTUAL_MODULE_ID = 'virtual:keywords';\nexport const RESOLVED_VIRTUAL_MODULE_ID = `\\0${VIRTUAL_MODULE_ID}`;\n\nexport interface KeywordsPluginOptions {\n additionalModulesToScan: string[];\n}\n\nexport const buildOptions = (\n options: Partial<KeywordsPluginOptions> = {},\n): KeywordsPluginOptions => {\n return {\n additionalModulesToScan: options.additionalModulesToScan || [],\n };\n};\n\nexport interface Logger {\n info: (message: string) => void;\n warn: (message: string) => void;\n error: (message: string) => void;\n}\n\nexport interface PrefixedLogger extends Logger {\n pluginName: string;\n}\n\nexport const createPrefixedLogger = (\n logger: Logger,\n pluginName: string,\n usePrefix: boolean = true,\n): PrefixedLogger => {\n const prefix = usePrefix ? `[${pluginName}] ` : '';\n const prefixed = (message: string) => `${prefix}${message}`;\n return {\n pluginName,\n info: (message: string) => logger.info(prefixed(message)),\n warn: (message: string) => logger.warn(prefixed(message)),\n error: (message: string) => logger.error(prefixed(message)),\n };\n};\n\n// ref: https://github.com/babel/babel/discussions/13093\nconst traverse =\n typeof _traverse === 'function'\n ? _traverse\n : ((_traverse as any).default as typeof _traverse);\n\nexport const extractKeywords = (\n code: string,\n additionalModulesToScan: string[] = [],\n): Set<string> => {\n const keywords = new Set<string>();\n\n // Fast-path: Skip parsing if no relevant imports are present in the code.\n const containsTargetModule =\n code.includes(VIRTUAL_MODULE_ID) ||\n additionalModulesToScan.some((moduleName) => code.includes(moduleName));\n\n if (!containsTargetModule) {\n return keywords;\n }\n\n let ast: Node;\n try {\n ast = parse(code, {\n sourceType: 'module',\n plugins: ['typescript', 'jsx'],\n errorRecovery: true,\n });\n } catch (e) {\n return keywords;\n }\n\n const keywordNamespaces = new Set<string>();\n\n traverse(ast, {\n enter(nodePath) {\n const node = nodePath.node;\n\n if (node.type === 'ImportDeclaration') {\n const isTargetModule =\n node.source.value === VIRTUAL_MODULE_ID ||\n additionalModulesToScan.includes(node.source.value);\n\n if (isTargetModule) {\n for (const specifier of node.specifiers) {\n if (specifier.type === 'ImportNamespaceSpecifier') {\n keywordNamespaces.add(specifier.local.name);\n }\n\n if (specifier.type === 'ImportDefaultSpecifier') {\n keywords.add('default');\n }\n\n if (specifier.type === 'ImportSpecifier') {\n if (specifier.imported.type === 'Identifier') {\n keywords.add(specifier.imported.name);\n }\n }\n }\n }\n }\n },\n });\n\n if (keywordNamespaces.size === 0) {\n return keywords;\n }\n\n traverse(ast, {\n enter(nodePath) {\n const node = nodePath.node;\n\n if (\n node.type === 'MemberExpression' &&\n !node.computed && // Exclude computed properties like K['xyz']\n node.object.type === 'Identifier' &&\n keywordNamespaces.has(node.object.name) &&\n node.property.type === 'Identifier'\n ) {\n keywords.add(node.property.name);\n }\n\n if (\n node.type === 'TSQualifiedName' &&\n node.left.type === 'Identifier' &&\n keywordNamespaces.has(node.left.name) &&\n node.right.type === 'Identifier'\n ) {\n keywords.add(node.right.name);\n }\n },\n });\n\n return keywords;\n};\n\nconst keywordConstPrefix = '_';\nconst createExportDeclaration = (keywords: Set<string>): string[] => {\n const aliases = [...keywords].map(\n (key) => ` ${keywordConstPrefix}${key} as ${key},`,\n );\n return [`export {`, ...aliases, `};`];\n};\n\nexport const generateTypesFile = async (\n collectedKeywords: Set<string>,\n root: string,\n dirname: string = '.keywords',\n filename: string = 'index.d.ts',\n): Promise<void> => {\n const keywordDeclarations = [...collectedKeywords]\n .map((key) => `declare const ${keywordConstPrefix}${key}: unique symbol;`)\n .join('\\n');\n const exportDeclaration =\n createExportDeclaration(collectedKeywords).join('\\n');\n const content = `${keywordDeclarations}\\n${exportDeclaration}\\n`;\n const pluginRoot = path.join(root, dirname);\n await mkdir(pluginRoot, { recursive: true });\n await writeFile(path.join(pluginRoot, filename), `${content.trim()}\\n`);\n};\n\nexport const collectKeywordsFromFiles = async (\n root: string,\n logger: PrefixedLogger,\n ignoredDirs: string[] = [],\n options: KeywordsPluginOptions = buildOptions(),\n): Promise<Set<string>> => {\n const collectedKeywords = new Set<string>();\n\n logger.info('Scanning project files for keywords...');\n\n const files = await globby('**/*.{js,ts,jsx,tsx}', {\n cwd: root,\n absolute: true,\n ignore: ['**/node_modules/**', ...ignoredDirs.map((dir) => `${dir}/**`)],\n gitignore: true,\n });\n\n const concurrency = 100;\n for (let i = 0; i < files.length; i += concurrency) {\n const chunk = files.slice(i, i + concurrency);\n await Promise.all(\n chunk.map(async (file) => {\n try {\n const code = await readFile(file, 'utf-8');\n const keywords = extractKeywords(\n code,\n options.additionalModulesToScan,\n );\n for (const key of keywords) {\n collectedKeywords.add(key);\n }\n } catch (error: any) {\n logger.warn(`Failed to process file ${file}: ${error.message}`);\n }\n }),\n );\n }\n\n logger.info(\n `Scan complete. Found ${collectedKeywords.size} unique keywords.`,\n );\n\n return collectedKeywords;\n};\n\nexport const collectKeywordsAndGenerateTypes = async (\n root: string,\n logger: PrefixedLogger,\n ignoredDirs?: string[],\n options?: KeywordsPluginOptions,\n): Promise<Set<string>> => {\n const collectedKeywords = await collectKeywordsFromFiles(\n root,\n logger,\n ignoredDirs,\n options,\n );\n await generateTypesFile(collectedKeywords, root);\n return collectedKeywords;\n};\n\nexport const generateModuleCode = (\n collectedKeywords: Set<string>,\n isDev: boolean,\n): string => {\n const symbolConstructorName = '__SYMBOL__';\n const symbolDeclaration = `const ${symbolConstructorName} = Symbol;`;\n const keywordDeclarations = [...collectedKeywords]\n .map(\n (key) =>\n `const ${keywordConstPrefix}${key} = /* @__PURE__ */ ${symbolConstructorName}(${isDev ? `'${key}'` : ''});`,\n )\n .join('\\n');\n const exportDeclaration =\n createExportDeclaration(collectedKeywords).join('\\n');\n return `${symbolDeclaration}\\n${keywordDeclarations}\\n${exportDeclaration}\\n`;\n};\n\nexport const splitQuery = (id: string) => id.split('?');\n","export const PLUGIN_NAME = 'vite-plugin-keywords';\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/cli.ts","../../minifiable-keywords/src/index.ts","../src/shared.ts"],"sourcesContent":["import { parseArgs } from 'node:util';\nimport {\n collectKeywordsAndGenerateTypes,\n createPrefixedLogger,\n} from 'minifiable-keywords';\nimport { resolveConfig } from 'vite';\nimport { PLUGIN_NAME } from './shared';\n\nconst main = async () => {\n const { values } = parseArgs({\n options: {\n config: {\n type: 'string',\n short: 'c',\n },\n },\n strict: false,\n });\n\n let config;\n try {\n config = await resolveConfig(\n {\n configFile:\n typeof values.config === 'string' ? values.config : undefined,\n },\n 'build',\n );\n } catch (error: any) {\n console.error(\n `[${PLUGIN_NAME}] Failed to resolve Vite config: ${error.message}`,\n );\n process.exit(1);\n }\n\n const logger = createPrefixedLogger(config.logger, PLUGIN_NAME);\n\n const keywordsPlugin = config.plugins.find(\n (plugin) => plugin.name === PLUGIN_NAME,\n );\n\n let pluginOptions;\n if (keywordsPlugin) {\n pluginOptions = keywordsPlugin.api?.options;\n } else {\n logger.error('Keywords plugin not found in Vite configuration.');\n process.exit(1);\n }\n\n try {\n await collectKeywordsAndGenerateTypes(\n config.root,\n logger,\n [config.build.outDir, config.cacheDir],\n pluginOptions,\n );\n } catch (error: any) {\n logger.error(\n `Failed to collect keywords and generate types: ${error.message}`,\n );\n process.exit(1);\n }\n};\n\nawait main();\n","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { parse } from '@babel/parser';\nimport _traverse, { type Node } from '@babel/traverse';\nimport { globby } from 'globby';\n\nexport const VIRTUAL_MODULE_ID = 'virtual:keywords';\nexport const RESOLVED_VIRTUAL_MODULE_ID = `\\0${VIRTUAL_MODULE_ID}`;\n\nexport interface KeywordsPluginOptions {\n additionalModulesToScan?: string[];\n isDev?: boolean;\n}\n\nexport type ResolvedKeywordsPluginOptions = Required<\n Omit<KeywordsPluginOptions, 'isDev'>\n> & { isDev?: boolean };\n\nexport const resolveOptions = (\n options?: KeywordsPluginOptions,\n): ResolvedKeywordsPluginOptions => {\n return {\n additionalModulesToScan: options?.additionalModulesToScan || [],\n isDev: options?.isDev,\n };\n};\n\nexport interface Logger {\n info: (message: string) => void;\n warn: (message: string) => void;\n error: (message: string) => void;\n}\n\nexport interface PrefixedLogger extends Logger {\n pluginName: string;\n}\n\nexport const createPrefixedLogger = (\n logger: Logger,\n pluginName: string,\n usePrefix: boolean = true,\n): PrefixedLogger => {\n const prefix = usePrefix ? `[${pluginName}] ` : '';\n const prefixed = (message: string) => `${prefix}${message}`;\n return {\n pluginName,\n info: (message: string) => logger.info(prefixed(message)),\n warn: (message: string) => logger.warn(prefixed(message)),\n error: (message: string) => logger.error(prefixed(message)),\n };\n};\n\n// ref: https://github.com/babel/babel/discussions/13093\nconst traverse =\n typeof _traverse === 'function'\n ? _traverse\n : ((_traverse as any).default as typeof _traverse);\n\nexport const extractKeywords = (\n code: string,\n additionalModulesToScan: string[] = [],\n): Set<string> => {\n const keywords = new Set<string>();\n\n // Fast-path: Skip parsing if no relevant imports are present in the code.\n const containsTargetModule =\n code.includes(VIRTUAL_MODULE_ID) ||\n additionalModulesToScan.some((moduleName) => code.includes(moduleName));\n\n if (!containsTargetModule) {\n return keywords;\n }\n\n let ast: Node;\n try {\n ast = parse(code, {\n sourceType: 'module',\n plugins: ['typescript', 'jsx'],\n errorRecovery: true,\n });\n } catch (e) {\n return keywords;\n }\n\n const keywordNamespaces = new Set<string>();\n\n traverse(ast, {\n enter(nodePath) {\n const node = nodePath.node;\n\n if (node.type === 'ImportDeclaration') {\n const isTargetModule =\n node.source.value === VIRTUAL_MODULE_ID ||\n additionalModulesToScan.includes(node.source.value);\n\n if (isTargetModule) {\n for (const specifier of node.specifiers) {\n if (specifier.type === 'ImportNamespaceSpecifier') {\n keywordNamespaces.add(specifier.local.name);\n }\n\n if (specifier.type === 'ImportDefaultSpecifier') {\n keywords.add('default');\n }\n\n if (specifier.type === 'ImportSpecifier') {\n if (specifier.imported.type === 'Identifier') {\n keywords.add(specifier.imported.name);\n }\n }\n }\n }\n }\n },\n });\n\n if (keywordNamespaces.size === 0) {\n return keywords;\n }\n\n traverse(ast, {\n enter(nodePath) {\n const node = nodePath.node;\n\n if (\n node.type === 'MemberExpression' &&\n !node.computed && // Exclude computed properties like K['xyz']\n node.object.type === 'Identifier' &&\n keywordNamespaces.has(node.object.name) &&\n node.property.type === 'Identifier'\n ) {\n keywords.add(node.property.name);\n }\n\n if (\n node.type === 'TSQualifiedName' &&\n node.left.type === 'Identifier' &&\n keywordNamespaces.has(node.left.name) &&\n node.right.type === 'Identifier'\n ) {\n keywords.add(node.right.name);\n }\n },\n });\n\n return keywords;\n};\n\nconst keywordConstPrefix = '_';\nconst createExportDeclaration = (keywords: Set<string>): string[] => {\n const aliases = [...keywords].map(\n (key) => ` ${keywordConstPrefix}${key} as ${key},`,\n );\n return [`export {`, ...aliases, `};`];\n};\n\nexport const generateTypesFile = async (\n collectedKeywords: Set<string>,\n root: string,\n dirname: string = '.keywords',\n filename: string = 'index.d.ts',\n): Promise<void> => {\n const keywordDeclarations = [...collectedKeywords]\n .map((key) => `declare const ${keywordConstPrefix}${key}: unique symbol;`)\n .join('\\n');\n const exportDeclaration =\n createExportDeclaration(collectedKeywords).join('\\n');\n const content = `${keywordDeclarations}\\n${exportDeclaration}\\n`;\n const pluginRoot = path.join(root, dirname);\n await mkdir(pluginRoot, { recursive: true });\n await writeFile(path.join(pluginRoot, filename), `${content.trim()}\\n`);\n};\n\nexport const collectKeywordsFromFiles = async (\n root: string,\n logger: PrefixedLogger,\n ignoredDirs: string[] = [],\n options?: KeywordsPluginOptions,\n): Promise<Set<string>> => {\n const resolvedOptions = resolveOptions(options);\n const collectedKeywords = new Set<string>();\n\n logger.info('Scanning project files for keywords...');\n\n const files = await globby('**/*.{js,ts,jsx,tsx}', {\n cwd: root,\n absolute: true,\n ignore: ['**/node_modules/**', ...ignoredDirs.map((dir) => `${dir}/**`)],\n gitignore: true,\n });\n\n const concurrency = 100;\n for (let i = 0; i < files.length; i += concurrency) {\n const chunk = files.slice(i, i + concurrency);\n await Promise.all(\n chunk.map(async (file) => {\n try {\n const code = await readFile(file, 'utf-8');\n const keywords = extractKeywords(\n code,\n resolvedOptions.additionalModulesToScan,\n );\n for (const key of keywords) {\n collectedKeywords.add(key);\n }\n } catch (error: any) {\n logger.warn(`Failed to process file ${file}: ${error.message}`);\n }\n }),\n );\n }\n\n logger.info(\n `Scan complete. Found ${collectedKeywords.size} unique keywords.`,\n );\n\n return collectedKeywords;\n};\n\nexport const collectKeywordsAndGenerateTypes = async (\n root: string,\n logger: PrefixedLogger,\n ignoredDirs?: string[],\n options?: KeywordsPluginOptions,\n): Promise<Set<string>> => {\n const collectedKeywords = await collectKeywordsFromFiles(\n root,\n logger,\n ignoredDirs,\n options,\n );\n await generateTypesFile(collectedKeywords, root);\n return collectedKeywords;\n};\n\nexport const generateModuleCode = (\n collectedKeywords: Set<string>,\n isDev: boolean,\n): string => {\n const symbolConstructorName = '__SYMBOL__';\n const symbolDeclaration = `const ${symbolConstructorName} = Symbol;`;\n const keywordDeclarations = [...collectedKeywords]\n .map(\n (key) =>\n `const ${keywordConstPrefix}${key} = /* @__PURE__ */ ${symbolConstructorName}(${isDev ? `'${key}'` : ''});`,\n )\n .join('\\n');\n const exportDeclaration =\n createExportDeclaration(collectedKeywords).join('\\n');\n return `${symbolDeclaration}\\n${keywordDeclarations}\\n${exportDeclaration}\\n`;\n};\n\nexport const splitQuery = (id: string) => id.split('?');\n","export const PLUGIN_NAME = 'vite-plugin-keywords';\n"],"mappings":"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","names":[]}
package/dist/index.cjs CHANGED
@@ -44,9 +44,10 @@ var import_traverse = __toESM(require("@babel/traverse"), 1);
44
44
  var import_globby = require("globby");
45
45
  var VIRTUAL_MODULE_ID = "virtual:keywords";
46
46
  var RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
47
- var buildOptions = (options = {}) => {
47
+ var resolveOptions = (options) => {
48
48
  return {
49
- additionalModulesToScan: options.additionalModulesToScan || []
49
+ additionalModulesToScan: options?.additionalModulesToScan || [],
50
+ isDev: options?.isDev
50
51
  };
51
52
  };
52
53
  var createPrefixedLogger = (logger, pluginName, usePrefix = true) => {
@@ -135,7 +136,8 @@ ${exportDeclaration}
135
136
  await (0, import_promises.writeFile)(import_path.default.join(pluginRoot, filename), `${content.trim()}
136
137
  `);
137
138
  };
138
- var collectKeywordsFromFiles = async (root, logger, ignoredDirs = [], options = buildOptions()) => {
139
+ var collectKeywordsFromFiles = async (root, logger, ignoredDirs = [], options) => {
140
+ const resolvedOptions = resolveOptions(options);
139
141
  const collectedKeywords = /* @__PURE__ */ new Set();
140
142
  logger.info("Scanning project files for keywords...");
141
143
  const files = await (0, import_globby.globby)("**/*.{js,ts,jsx,tsx}", {
@@ -153,7 +155,7 @@ var collectKeywordsFromFiles = async (root, logger, ignoredDirs = [], options =
153
155
  const code = await (0, import_promises.readFile)(file, "utf-8");
154
156
  const keywords = extractKeywords(
155
157
  code,
156
- options.additionalModulesToScan
158
+ resolvedOptions.additionalModulesToScan
157
159
  );
158
160
  for (const key of keywords) {
159
161
  collectedKeywords.add(key);
@@ -198,7 +200,7 @@ var PLUGIN_NAME = "vite-plugin-keywords";
198
200
 
199
201
  // src/index.ts
200
202
  var keywordsPlugin = (options) => {
201
- const pluginOptions = buildOptions(options);
203
+ const pluginOptions = resolveOptions(options);
202
204
  let collectedKeywords;
203
205
  let config;
204
206
  let logger;
@@ -238,7 +240,7 @@ var keywordsPlugin = (options) => {
238
240
  load(id) {
239
241
  const [validId] = splitQuery(id);
240
242
  if (validId === RESOLVED_VIRTUAL_MODULE_ID) {
241
- const isDev = config.mode === "development";
243
+ const isDev = pluginOptions.isDev ?? config.mode === "development";
242
244
  return generateModuleCode(collectedKeywords, isDev);
243
245
  }
244
246
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../../minifiable-keywords/src/index.ts","../src/shared.ts"],"sourcesContent":["import path from 'node:path';\nimport {\n buildOptions,\n collectKeywordsAndGenerateTypes,\n createPrefixedLogger,\n extractKeywords,\n generateModuleCode,\n generateTypesFile,\n RESOLVED_VIRTUAL_MODULE_ID,\n splitQuery,\n VIRTUAL_MODULE_ID,\n type KeywordsPluginOptions,\n type PrefixedLogger,\n} from 'minifiable-keywords';\nimport type { EnvironmentModuleGraph, Plugin, ResolvedConfig } from 'vite';\nimport { PLUGIN_NAME } from './shared';\n\nexport type { KeywordsPluginOptions } from 'minifiable-keywords';\n\nexport const keywordsPlugin = (\n options?: Partial<KeywordsPluginOptions>,\n): Plugin => {\n const pluginOptions = buildOptions(options);\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 api: {\n options: pluginOptions,\n },\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 pluginOptions,\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(\n code,\n pluginOptions.additionalModulesToScan,\n );\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","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { parse } from '@babel/parser';\nimport _traverse, { type Node } from '@babel/traverse';\nimport { globby } from 'globby';\n\nexport const VIRTUAL_MODULE_ID = 'virtual:keywords';\nexport const RESOLVED_VIRTUAL_MODULE_ID = `\\0${VIRTUAL_MODULE_ID}`;\n\nexport interface KeywordsPluginOptions {\n additionalModulesToScan: string[];\n}\n\nexport const buildOptions = (\n options: Partial<KeywordsPluginOptions> = {},\n): KeywordsPluginOptions => {\n return {\n additionalModulesToScan: options.additionalModulesToScan || [],\n };\n};\n\nexport interface Logger {\n info: (message: string) => void;\n warn: (message: string) => void;\n error: (message: string) => void;\n}\n\nexport interface PrefixedLogger extends Logger {\n pluginName: string;\n}\n\nexport const createPrefixedLogger = (\n logger: Logger,\n pluginName: string,\n usePrefix: boolean = true,\n): PrefixedLogger => {\n const prefix = usePrefix ? `[${pluginName}] ` : '';\n const prefixed = (message: string) => `${prefix}${message}`;\n return {\n pluginName,\n info: (message: string) => logger.info(prefixed(message)),\n warn: (message: string) => logger.warn(prefixed(message)),\n error: (message: string) => logger.error(prefixed(message)),\n };\n};\n\n// ref: https://github.com/babel/babel/discussions/13093\nconst traverse =\n typeof _traverse === 'function'\n ? _traverse\n : ((_traverse as any).default as typeof _traverse);\n\nexport const extractKeywords = (\n code: string,\n additionalModulesToScan: string[] = [],\n): Set<string> => {\n const keywords = new Set<string>();\n\n // Fast-path: Skip parsing if no relevant imports are present in the code.\n const containsTargetModule =\n code.includes(VIRTUAL_MODULE_ID) ||\n additionalModulesToScan.some((moduleName) => code.includes(moduleName));\n\n if (!containsTargetModule) {\n return keywords;\n }\n\n let ast: Node;\n try {\n ast = parse(code, {\n sourceType: 'module',\n plugins: ['typescript', 'jsx'],\n errorRecovery: true,\n });\n } catch (e) {\n return keywords;\n }\n\n const keywordNamespaces = new Set<string>();\n\n traverse(ast, {\n enter(nodePath) {\n const node = nodePath.node;\n\n if (node.type === 'ImportDeclaration') {\n const isTargetModule =\n node.source.value === VIRTUAL_MODULE_ID ||\n additionalModulesToScan.includes(node.source.value);\n\n if (isTargetModule) {\n for (const specifier of node.specifiers) {\n if (specifier.type === 'ImportNamespaceSpecifier') {\n keywordNamespaces.add(specifier.local.name);\n }\n\n if (specifier.type === 'ImportDefaultSpecifier') {\n keywords.add('default');\n }\n\n if (specifier.type === 'ImportSpecifier') {\n if (specifier.imported.type === 'Identifier') {\n keywords.add(specifier.imported.name);\n }\n }\n }\n }\n }\n },\n });\n\n if (keywordNamespaces.size === 0) {\n return keywords;\n }\n\n traverse(ast, {\n enter(nodePath) {\n const node = nodePath.node;\n\n if (\n node.type === 'MemberExpression' &&\n !node.computed && // Exclude computed properties like K['xyz']\n node.object.type === 'Identifier' &&\n keywordNamespaces.has(node.object.name) &&\n node.property.type === 'Identifier'\n ) {\n keywords.add(node.property.name);\n }\n\n if (\n node.type === 'TSQualifiedName' &&\n node.left.type === 'Identifier' &&\n keywordNamespaces.has(node.left.name) &&\n node.right.type === 'Identifier'\n ) {\n keywords.add(node.right.name);\n }\n },\n });\n\n return keywords;\n};\n\nconst keywordConstPrefix = '_';\nconst createExportDeclaration = (keywords: Set<string>): string[] => {\n const aliases = [...keywords].map(\n (key) => ` ${keywordConstPrefix}${key} as ${key},`,\n );\n return [`export {`, ...aliases, `};`];\n};\n\nexport const generateTypesFile = async (\n collectedKeywords: Set<string>,\n root: string,\n dirname: string = '.keywords',\n filename: string = 'index.d.ts',\n): Promise<void> => {\n const keywordDeclarations = [...collectedKeywords]\n .map((key) => `declare const ${keywordConstPrefix}${key}: unique symbol;`)\n .join('\\n');\n const exportDeclaration =\n createExportDeclaration(collectedKeywords).join('\\n');\n const content = `${keywordDeclarations}\\n${exportDeclaration}\\n`;\n const pluginRoot = path.join(root, dirname);\n await mkdir(pluginRoot, { recursive: true });\n await writeFile(path.join(pluginRoot, filename), `${content.trim()}\\n`);\n};\n\nexport const collectKeywordsFromFiles = async (\n root: string,\n logger: PrefixedLogger,\n ignoredDirs: string[] = [],\n options: KeywordsPluginOptions = buildOptions(),\n): Promise<Set<string>> => {\n const collectedKeywords = new Set<string>();\n\n logger.info('Scanning project files for keywords...');\n\n const files = await globby('**/*.{js,ts,jsx,tsx}', {\n cwd: root,\n absolute: true,\n ignore: ['**/node_modules/**', ...ignoredDirs.map((dir) => `${dir}/**`)],\n gitignore: true,\n });\n\n const concurrency = 100;\n for (let i = 0; i < files.length; i += concurrency) {\n const chunk = files.slice(i, i + concurrency);\n await Promise.all(\n chunk.map(async (file) => {\n try {\n const code = await readFile(file, 'utf-8');\n const keywords = extractKeywords(\n code,\n options.additionalModulesToScan,\n );\n for (const key of keywords) {\n collectedKeywords.add(key);\n }\n } catch (error: any) {\n logger.warn(`Failed to process file ${file}: ${error.message}`);\n }\n }),\n );\n }\n\n logger.info(\n `Scan complete. Found ${collectedKeywords.size} unique keywords.`,\n );\n\n return collectedKeywords;\n};\n\nexport const collectKeywordsAndGenerateTypes = async (\n root: string,\n logger: PrefixedLogger,\n ignoredDirs?: string[],\n options?: KeywordsPluginOptions,\n): Promise<Set<string>> => {\n const collectedKeywords = await collectKeywordsFromFiles(\n root,\n logger,\n ignoredDirs,\n options,\n );\n await generateTypesFile(collectedKeywords, root);\n return collectedKeywords;\n};\n\nexport const generateModuleCode = (\n collectedKeywords: Set<string>,\n isDev: boolean,\n): string => {\n const symbolConstructorName = '__SYMBOL__';\n const symbolDeclaration = `const ${symbolConstructorName} = Symbol;`;\n const keywordDeclarations = [...collectedKeywords]\n .map(\n (key) =>\n `const ${keywordConstPrefix}${key} = /* @__PURE__ */ ${symbolConstructorName}(${isDev ? `'${key}'` : ''});`,\n )\n .join('\\n');\n const exportDeclaration =\n createExportDeclaration(collectedKeywords).join('\\n');\n return `${symbolDeclaration}\\n${keywordDeclarations}\\n${exportDeclaration}\\n`;\n};\n\nexport const splitQuery = (id: string) => id.split('?');\n","export const PLUGIN_NAME = 'vite-plugin-keywords';\n"],"mappings":"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","names":["_traverse","path","module","path"]}
1
+ {"version":3,"sources":["../src/index.ts","../../minifiable-keywords/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 resolveOptions,\n splitQuery,\n VIRTUAL_MODULE_ID,\n type KeywordsPluginOptions,\n type PrefixedLogger,\n} from 'minifiable-keywords';\nimport type { EnvironmentModuleGraph, Plugin, ResolvedConfig } from 'vite';\nimport { PLUGIN_NAME } from './shared';\n\nexport type { KeywordsPluginOptions } from 'minifiable-keywords';\n\nexport const keywordsPlugin = (options?: KeywordsPluginOptions): Plugin => {\n const pluginOptions = resolveOptions(options);\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 api: {\n options: pluginOptions,\n },\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 pluginOptions,\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 = pluginOptions.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(\n code,\n pluginOptions.additionalModulesToScan,\n );\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","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { parse } from '@babel/parser';\nimport _traverse, { type Node } from '@babel/traverse';\nimport { globby } from 'globby';\n\nexport const VIRTUAL_MODULE_ID = 'virtual:keywords';\nexport const RESOLVED_VIRTUAL_MODULE_ID = `\\0${VIRTUAL_MODULE_ID}`;\n\nexport interface KeywordsPluginOptions {\n additionalModulesToScan?: string[];\n isDev?: boolean;\n}\n\nexport type ResolvedKeywordsPluginOptions = Required<\n Omit<KeywordsPluginOptions, 'isDev'>\n> & { isDev?: boolean };\n\nexport const resolveOptions = (\n options?: KeywordsPluginOptions,\n): ResolvedKeywordsPluginOptions => {\n return {\n additionalModulesToScan: options?.additionalModulesToScan || [],\n isDev: options?.isDev,\n };\n};\n\nexport interface Logger {\n info: (message: string) => void;\n warn: (message: string) => void;\n error: (message: string) => void;\n}\n\nexport interface PrefixedLogger extends Logger {\n pluginName: string;\n}\n\nexport const createPrefixedLogger = (\n logger: Logger,\n pluginName: string,\n usePrefix: boolean = true,\n): PrefixedLogger => {\n const prefix = usePrefix ? `[${pluginName}] ` : '';\n const prefixed = (message: string) => `${prefix}${message}`;\n return {\n pluginName,\n info: (message: string) => logger.info(prefixed(message)),\n warn: (message: string) => logger.warn(prefixed(message)),\n error: (message: string) => logger.error(prefixed(message)),\n };\n};\n\n// ref: https://github.com/babel/babel/discussions/13093\nconst traverse =\n typeof _traverse === 'function'\n ? _traverse\n : ((_traverse as any).default as typeof _traverse);\n\nexport const extractKeywords = (\n code: string,\n additionalModulesToScan: string[] = [],\n): Set<string> => {\n const keywords = new Set<string>();\n\n // Fast-path: Skip parsing if no relevant imports are present in the code.\n const containsTargetModule =\n code.includes(VIRTUAL_MODULE_ID) ||\n additionalModulesToScan.some((moduleName) => code.includes(moduleName));\n\n if (!containsTargetModule) {\n return keywords;\n }\n\n let ast: Node;\n try {\n ast = parse(code, {\n sourceType: 'module',\n plugins: ['typescript', 'jsx'],\n errorRecovery: true,\n });\n } catch (e) {\n return keywords;\n }\n\n const keywordNamespaces = new Set<string>();\n\n traverse(ast, {\n enter(nodePath) {\n const node = nodePath.node;\n\n if (node.type === 'ImportDeclaration') {\n const isTargetModule =\n node.source.value === VIRTUAL_MODULE_ID ||\n additionalModulesToScan.includes(node.source.value);\n\n if (isTargetModule) {\n for (const specifier of node.specifiers) {\n if (specifier.type === 'ImportNamespaceSpecifier') {\n keywordNamespaces.add(specifier.local.name);\n }\n\n if (specifier.type === 'ImportDefaultSpecifier') {\n keywords.add('default');\n }\n\n if (specifier.type === 'ImportSpecifier') {\n if (specifier.imported.type === 'Identifier') {\n keywords.add(specifier.imported.name);\n }\n }\n }\n }\n }\n },\n });\n\n if (keywordNamespaces.size === 0) {\n return keywords;\n }\n\n traverse(ast, {\n enter(nodePath) {\n const node = nodePath.node;\n\n if (\n node.type === 'MemberExpression' &&\n !node.computed && // Exclude computed properties like K['xyz']\n node.object.type === 'Identifier' &&\n keywordNamespaces.has(node.object.name) &&\n node.property.type === 'Identifier'\n ) {\n keywords.add(node.property.name);\n }\n\n if (\n node.type === 'TSQualifiedName' &&\n node.left.type === 'Identifier' &&\n keywordNamespaces.has(node.left.name) &&\n node.right.type === 'Identifier'\n ) {\n keywords.add(node.right.name);\n }\n },\n });\n\n return keywords;\n};\n\nconst keywordConstPrefix = '_';\nconst createExportDeclaration = (keywords: Set<string>): string[] => {\n const aliases = [...keywords].map(\n (key) => ` ${keywordConstPrefix}${key} as ${key},`,\n );\n return [`export {`, ...aliases, `};`];\n};\n\nexport const generateTypesFile = async (\n collectedKeywords: Set<string>,\n root: string,\n dirname: string = '.keywords',\n filename: string = 'index.d.ts',\n): Promise<void> => {\n const keywordDeclarations = [...collectedKeywords]\n .map((key) => `declare const ${keywordConstPrefix}${key}: unique symbol;`)\n .join('\\n');\n const exportDeclaration =\n createExportDeclaration(collectedKeywords).join('\\n');\n const content = `${keywordDeclarations}\\n${exportDeclaration}\\n`;\n const pluginRoot = path.join(root, dirname);\n await mkdir(pluginRoot, { recursive: true });\n await writeFile(path.join(pluginRoot, filename), `${content.trim()}\\n`);\n};\n\nexport const collectKeywordsFromFiles = async (\n root: string,\n logger: PrefixedLogger,\n ignoredDirs: string[] = [],\n options?: KeywordsPluginOptions,\n): Promise<Set<string>> => {\n const resolvedOptions = resolveOptions(options);\n const collectedKeywords = new Set<string>();\n\n logger.info('Scanning project files for keywords...');\n\n const files = await globby('**/*.{js,ts,jsx,tsx}', {\n cwd: root,\n absolute: true,\n ignore: ['**/node_modules/**', ...ignoredDirs.map((dir) => `${dir}/**`)],\n gitignore: true,\n });\n\n const concurrency = 100;\n for (let i = 0; i < files.length; i += concurrency) {\n const chunk = files.slice(i, i + concurrency);\n await Promise.all(\n chunk.map(async (file) => {\n try {\n const code = await readFile(file, 'utf-8');\n const keywords = extractKeywords(\n code,\n resolvedOptions.additionalModulesToScan,\n );\n for (const key of keywords) {\n collectedKeywords.add(key);\n }\n } catch (error: any) {\n logger.warn(`Failed to process file ${file}: ${error.message}`);\n }\n }),\n );\n }\n\n logger.info(\n `Scan complete. Found ${collectedKeywords.size} unique keywords.`,\n );\n\n return collectedKeywords;\n};\n\nexport const collectKeywordsAndGenerateTypes = async (\n root: string,\n logger: PrefixedLogger,\n ignoredDirs?: string[],\n options?: KeywordsPluginOptions,\n): Promise<Set<string>> => {\n const collectedKeywords = await collectKeywordsFromFiles(\n root,\n logger,\n ignoredDirs,\n options,\n );\n await generateTypesFile(collectedKeywords, root);\n return collectedKeywords;\n};\n\nexport const generateModuleCode = (\n collectedKeywords: Set<string>,\n isDev: boolean,\n): string => {\n const symbolConstructorName = '__SYMBOL__';\n const symbolDeclaration = `const ${symbolConstructorName} = Symbol;`;\n const keywordDeclarations = [...collectedKeywords]\n .map(\n (key) =>\n `const ${keywordConstPrefix}${key} = /* @__PURE__ */ ${symbolConstructorName}(${isDev ? `'${key}'` : ''});`,\n )\n .join('\\n');\n const exportDeclaration =\n createExportDeclaration(collectedKeywords).join('\\n');\n return `${symbolDeclaration}\\n${keywordDeclarations}\\n${exportDeclaration}\\n`;\n};\n\nexport const splitQuery = (id: string) => id.split('?');\n","export const PLUGIN_NAME = 'vite-plugin-keywords';\n"],"mappings":"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","names":["_traverse","path","module","path"]}
package/dist/index.d.cts CHANGED
@@ -1,7 +1,10 @@
1
- import { KeywordsPluginOptions } from 'minifiable-keywords';
2
- export { KeywordsPluginOptions } from 'minifiable-keywords';
3
1
  import { Plugin } from 'vite';
4
2
 
5
- declare const keywordsPlugin: (options?: Partial<KeywordsPluginOptions>) => Plugin;
3
+ interface KeywordsPluginOptions {
4
+ additionalModulesToScan?: string[];
5
+ isDev?: boolean;
6
+ }
6
7
 
7
- export { keywordsPlugin as default, keywordsPlugin };
8
+ declare const keywordsPlugin: (options?: KeywordsPluginOptions) => Plugin;
9
+
10
+ export { type KeywordsPluginOptions, keywordsPlugin as default, keywordsPlugin };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,10 @@
1
- import { KeywordsPluginOptions } from 'minifiable-keywords';
2
- export { KeywordsPluginOptions } from 'minifiable-keywords';
3
1
  import { Plugin } from 'vite';
4
2
 
5
- declare const keywordsPlugin: (options?: Partial<KeywordsPluginOptions>) => Plugin;
3
+ interface KeywordsPluginOptions {
4
+ additionalModulesToScan?: string[];
5
+ isDev?: boolean;
6
+ }
6
7
 
7
- export { keywordsPlugin as default, keywordsPlugin };
8
+ declare const keywordsPlugin: (options?: KeywordsPluginOptions) => Plugin;
9
+
10
+ export { type KeywordsPluginOptions, keywordsPlugin as default, keywordsPlugin };
package/dist/index.js CHANGED
@@ -9,9 +9,10 @@ import _traverse from "@babel/traverse";
9
9
  import { globby } from "globby";
10
10
  var VIRTUAL_MODULE_ID = "virtual:keywords";
11
11
  var RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
12
- var buildOptions = (options = {}) => {
12
+ var resolveOptions = (options) => {
13
13
  return {
14
- additionalModulesToScan: options.additionalModulesToScan || []
14
+ additionalModulesToScan: options?.additionalModulesToScan || [],
15
+ isDev: options?.isDev
15
16
  };
16
17
  };
17
18
  var createPrefixedLogger = (logger, pluginName, usePrefix = true) => {
@@ -100,7 +101,8 @@ ${exportDeclaration}
100
101
  await writeFile(path.join(pluginRoot, filename), `${content.trim()}
101
102
  `);
102
103
  };
103
- var collectKeywordsFromFiles = async (root, logger, ignoredDirs = [], options = buildOptions()) => {
104
+ var collectKeywordsFromFiles = async (root, logger, ignoredDirs = [], options) => {
105
+ const resolvedOptions = resolveOptions(options);
104
106
  const collectedKeywords = /* @__PURE__ */ new Set();
105
107
  logger.info("Scanning project files for keywords...");
106
108
  const files = await globby("**/*.{js,ts,jsx,tsx}", {
@@ -118,7 +120,7 @@ var collectKeywordsFromFiles = async (root, logger, ignoredDirs = [], options =
118
120
  const code = await readFile(file, "utf-8");
119
121
  const keywords = extractKeywords(
120
122
  code,
121
- options.additionalModulesToScan
123
+ resolvedOptions.additionalModulesToScan
122
124
  );
123
125
  for (const key of keywords) {
124
126
  collectedKeywords.add(key);
@@ -163,7 +165,7 @@ var PLUGIN_NAME = "vite-plugin-keywords";
163
165
 
164
166
  // src/index.ts
165
167
  var keywordsPlugin = (options) => {
166
- const pluginOptions = buildOptions(options);
168
+ const pluginOptions = resolveOptions(options);
167
169
  let collectedKeywords;
168
170
  let config;
169
171
  let logger;
@@ -203,7 +205,7 @@ var keywordsPlugin = (options) => {
203
205
  load(id) {
204
206
  const [validId] = splitQuery(id);
205
207
  if (validId === RESOLVED_VIRTUAL_MODULE_ID) {
206
- const isDev = config.mode === "development";
208
+ const isDev = pluginOptions.isDev ?? config.mode === "development";
207
209
  return generateModuleCode(collectedKeywords, isDev);
208
210
  }
209
211
  },
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../../minifiable-keywords/src/index.ts","../src/shared.ts"],"sourcesContent":["import path from 'node:path';\nimport {\n buildOptions,\n collectKeywordsAndGenerateTypes,\n createPrefixedLogger,\n extractKeywords,\n generateModuleCode,\n generateTypesFile,\n RESOLVED_VIRTUAL_MODULE_ID,\n splitQuery,\n VIRTUAL_MODULE_ID,\n type KeywordsPluginOptions,\n type PrefixedLogger,\n} from 'minifiable-keywords';\nimport type { EnvironmentModuleGraph, Plugin, ResolvedConfig } from 'vite';\nimport { PLUGIN_NAME } from './shared';\n\nexport type { KeywordsPluginOptions } from 'minifiable-keywords';\n\nexport const keywordsPlugin = (\n options?: Partial<KeywordsPluginOptions>,\n): Plugin => {\n const pluginOptions = buildOptions(options);\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 api: {\n options: pluginOptions,\n },\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 pluginOptions,\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(\n code,\n pluginOptions.additionalModulesToScan,\n );\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","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { parse } from '@babel/parser';\nimport _traverse, { type Node } from '@babel/traverse';\nimport { globby } from 'globby';\n\nexport const VIRTUAL_MODULE_ID = 'virtual:keywords';\nexport const RESOLVED_VIRTUAL_MODULE_ID = `\\0${VIRTUAL_MODULE_ID}`;\n\nexport interface KeywordsPluginOptions {\n additionalModulesToScan: string[];\n}\n\nexport const buildOptions = (\n options: Partial<KeywordsPluginOptions> = {},\n): KeywordsPluginOptions => {\n return {\n additionalModulesToScan: options.additionalModulesToScan || [],\n };\n};\n\nexport interface Logger {\n info: (message: string) => void;\n warn: (message: string) => void;\n error: (message: string) => void;\n}\n\nexport interface PrefixedLogger extends Logger {\n pluginName: string;\n}\n\nexport const createPrefixedLogger = (\n logger: Logger,\n pluginName: string,\n usePrefix: boolean = true,\n): PrefixedLogger => {\n const prefix = usePrefix ? `[${pluginName}] ` : '';\n const prefixed = (message: string) => `${prefix}${message}`;\n return {\n pluginName,\n info: (message: string) => logger.info(prefixed(message)),\n warn: (message: string) => logger.warn(prefixed(message)),\n error: (message: string) => logger.error(prefixed(message)),\n };\n};\n\n// ref: https://github.com/babel/babel/discussions/13093\nconst traverse =\n typeof _traverse === 'function'\n ? _traverse\n : ((_traverse as any).default as typeof _traverse);\n\nexport const extractKeywords = (\n code: string,\n additionalModulesToScan: string[] = [],\n): Set<string> => {\n const keywords = new Set<string>();\n\n // Fast-path: Skip parsing if no relevant imports are present in the code.\n const containsTargetModule =\n code.includes(VIRTUAL_MODULE_ID) ||\n additionalModulesToScan.some((moduleName) => code.includes(moduleName));\n\n if (!containsTargetModule) {\n return keywords;\n }\n\n let ast: Node;\n try {\n ast = parse(code, {\n sourceType: 'module',\n plugins: ['typescript', 'jsx'],\n errorRecovery: true,\n });\n } catch (e) {\n return keywords;\n }\n\n const keywordNamespaces = new Set<string>();\n\n traverse(ast, {\n enter(nodePath) {\n const node = nodePath.node;\n\n if (node.type === 'ImportDeclaration') {\n const isTargetModule =\n node.source.value === VIRTUAL_MODULE_ID ||\n additionalModulesToScan.includes(node.source.value);\n\n if (isTargetModule) {\n for (const specifier of node.specifiers) {\n if (specifier.type === 'ImportNamespaceSpecifier') {\n keywordNamespaces.add(specifier.local.name);\n }\n\n if (specifier.type === 'ImportDefaultSpecifier') {\n keywords.add('default');\n }\n\n if (specifier.type === 'ImportSpecifier') {\n if (specifier.imported.type === 'Identifier') {\n keywords.add(specifier.imported.name);\n }\n }\n }\n }\n }\n },\n });\n\n if (keywordNamespaces.size === 0) {\n return keywords;\n }\n\n traverse(ast, {\n enter(nodePath) {\n const node = nodePath.node;\n\n if (\n node.type === 'MemberExpression' &&\n !node.computed && // Exclude computed properties like K['xyz']\n node.object.type === 'Identifier' &&\n keywordNamespaces.has(node.object.name) &&\n node.property.type === 'Identifier'\n ) {\n keywords.add(node.property.name);\n }\n\n if (\n node.type === 'TSQualifiedName' &&\n node.left.type === 'Identifier' &&\n keywordNamespaces.has(node.left.name) &&\n node.right.type === 'Identifier'\n ) {\n keywords.add(node.right.name);\n }\n },\n });\n\n return keywords;\n};\n\nconst keywordConstPrefix = '_';\nconst createExportDeclaration = (keywords: Set<string>): string[] => {\n const aliases = [...keywords].map(\n (key) => ` ${keywordConstPrefix}${key} as ${key},`,\n );\n return [`export {`, ...aliases, `};`];\n};\n\nexport const generateTypesFile = async (\n collectedKeywords: Set<string>,\n root: string,\n dirname: string = '.keywords',\n filename: string = 'index.d.ts',\n): Promise<void> => {\n const keywordDeclarations = [...collectedKeywords]\n .map((key) => `declare const ${keywordConstPrefix}${key}: unique symbol;`)\n .join('\\n');\n const exportDeclaration =\n createExportDeclaration(collectedKeywords).join('\\n');\n const content = `${keywordDeclarations}\\n${exportDeclaration}\\n`;\n const pluginRoot = path.join(root, dirname);\n await mkdir(pluginRoot, { recursive: true });\n await writeFile(path.join(pluginRoot, filename), `${content.trim()}\\n`);\n};\n\nexport const collectKeywordsFromFiles = async (\n root: string,\n logger: PrefixedLogger,\n ignoredDirs: string[] = [],\n options: KeywordsPluginOptions = buildOptions(),\n): Promise<Set<string>> => {\n const collectedKeywords = new Set<string>();\n\n logger.info('Scanning project files for keywords...');\n\n const files = await globby('**/*.{js,ts,jsx,tsx}', {\n cwd: root,\n absolute: true,\n ignore: ['**/node_modules/**', ...ignoredDirs.map((dir) => `${dir}/**`)],\n gitignore: true,\n });\n\n const concurrency = 100;\n for (let i = 0; i < files.length; i += concurrency) {\n const chunk = files.slice(i, i + concurrency);\n await Promise.all(\n chunk.map(async (file) => {\n try {\n const code = await readFile(file, 'utf-8');\n const keywords = extractKeywords(\n code,\n options.additionalModulesToScan,\n );\n for (const key of keywords) {\n collectedKeywords.add(key);\n }\n } catch (error: any) {\n logger.warn(`Failed to process file ${file}: ${error.message}`);\n }\n }),\n );\n }\n\n logger.info(\n `Scan complete. Found ${collectedKeywords.size} unique keywords.`,\n );\n\n return collectedKeywords;\n};\n\nexport const collectKeywordsAndGenerateTypes = async (\n root: string,\n logger: PrefixedLogger,\n ignoredDirs?: string[],\n options?: KeywordsPluginOptions,\n): Promise<Set<string>> => {\n const collectedKeywords = await collectKeywordsFromFiles(\n root,\n logger,\n ignoredDirs,\n options,\n );\n await generateTypesFile(collectedKeywords, root);\n return collectedKeywords;\n};\n\nexport const generateModuleCode = (\n collectedKeywords: Set<string>,\n isDev: boolean,\n): string => {\n const symbolConstructorName = '__SYMBOL__';\n const symbolDeclaration = `const ${symbolConstructorName} = Symbol;`;\n const keywordDeclarations = [...collectedKeywords]\n .map(\n (key) =>\n `const ${keywordConstPrefix}${key} = /* @__PURE__ */ ${symbolConstructorName}(${isDev ? `'${key}'` : ''});`,\n )\n .join('\\n');\n const exportDeclaration =\n createExportDeclaration(collectedKeywords).join('\\n');\n return `${symbolDeclaration}\\n${keywordDeclarations}\\n${exportDeclaration}\\n`;\n};\n\nexport const splitQuery = (id: string) => id.split('?');\n","export const PLUGIN_NAME = 'vite-plugin-keywords';\n"],"mappings":";AAAA,OAAOA,WAAU;;;ACAjB,SAAS,OAAO,UAAU,iBAAiB;AAC3C,OAAO,UAAU;AACjB,SAAS,aAAa;AACtB,OAAO,eAA8B;AACrC,SAAS,cAAc;AAEhB,IAAM,oBAAoB;AAC1B,IAAM,6BAA6B,KAAK,iBAAiB;AAMzD,IAAM,eAAe,CAC1B,UAA0C,CAAC,MACjB;AAC1B,SAAO;IACL,yBAAyB,QAAQ,2BAA2B,CAAC;EAC/D;AACF;AAYO,IAAM,uBAAuB,CAClC,QACA,YACA,YAAqB,SACF;AACnB,QAAM,SAAS,YAAY,IAAI,UAAU,OAAO;AAChD,QAAM,WAAW,CAAC,YAAoB,GAAG,MAAM,GAAG,OAAO;AACzD,SAAO;IACL;IACA,MAAM,CAAC,YAAoB,OAAO,KAAK,SAAS,OAAO,CAAC;IACxD,MAAM,CAAC,YAAoB,OAAO,KAAK,SAAS,OAAO,CAAC;IACxD,OAAO,CAAC,YAAoB,OAAO,MAAM,SAAS,OAAO,CAAC;EAC5D;AACF;AAGA,IAAM,WACJ,OAAO,cAAc,aACjB,YACE,UAAkB;AAEnB,IAAM,kBAAkB,CAC7B,MACA,0BAAoC,CAAC,MACrB;AAChB,QAAM,WAAW,oBAAI,IAAY;AAGjC,QAAM,uBACJ,KAAK,SAAS,iBAAiB,KAC/B,wBAAwB,KAAK,CAAC,eAAe,KAAK,SAAS,UAAU,CAAC;AAExE,MAAI,CAAC,sBAAsB;AACzB,WAAO;EACT;AAEA,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,MAAM;MAChB,YAAY;MACZ,SAAS,CAAC,cAAc,KAAK;MAC7B,eAAe;IACjB,CAAC;EACH,SAAS,GAAG;AACV,WAAO;EACT;AAEA,QAAM,oBAAoB,oBAAI,IAAY;AAE1C,WAAS,KAAK;IACZ,MAAM,UAAU;AACd,YAAM,OAAO,SAAS;AAEtB,UAAI,KAAK,SAAS,qBAAqB;AACrC,cAAM,iBACJ,KAAK,OAAO,UAAU,qBACtB,wBAAwB,SAAS,KAAK,OAAO,KAAK;AAEpD,YAAI,gBAAgB;AAClB,qBAAW,aAAa,KAAK,YAAY;AACvC,gBAAI,UAAU,SAAS,4BAA4B;AACjD,gCAAkB,IAAI,UAAU,MAAM,IAAI;YAC5C;AAEA,gBAAI,UAAU,SAAS,0BAA0B;AAC/C,uBAAS,IAAI,SAAS;YACxB;AAEA,gBAAI,UAAU,SAAS,mBAAmB;AACxC,kBAAI,UAAU,SAAS,SAAS,cAAc;AAC5C,yBAAS,IAAI,UAAU,SAAS,IAAI;cACtC;YACF;UACF;QACF;MACF;IACF;EACF,CAAC;AAED,MAAI,kBAAkB,SAAS,GAAG;AAChC,WAAO;EACT;AAEA,WAAS,KAAK;IACZ,MAAM,UAAU;AACd,YAAM,OAAO,SAAS;AAEtB,UACE,KAAK,SAAS,sBACd,CAAC,KAAK;MACN,KAAK,OAAO,SAAS,gBACrB,kBAAkB,IAAI,KAAK,OAAO,IAAI,KACtC,KAAK,SAAS,SAAS,cACvB;AACA,iBAAS,IAAI,KAAK,SAAS,IAAI;MACjC;AAEA,UACE,KAAK,SAAS,qBACd,KAAK,KAAK,SAAS,gBACnB,kBAAkB,IAAI,KAAK,KAAK,IAAI,KACpC,KAAK,MAAM,SAAS,cACpB;AACA,iBAAS,IAAI,KAAK,MAAM,IAAI;MAC9B;IACF;EACF,CAAC;AAED,SAAO;AACT;AAEA,IAAM,qBAAqB;AAC3B,IAAM,0BAA0B,CAAC,aAAoC;AACnE,QAAM,UAAU,CAAC,GAAG,QAAQ,EAAE;IAC5B,CAAC,QAAQ,KAAK,kBAAkB,GAAG,GAAG,OAAO,GAAG;EAClD;AACA,SAAO,CAAC,YAAY,GAAG,SAAS,IAAI;AACtC;AAEO,IAAM,oBAAoB,OAC/B,mBACA,MACA,UAAkB,aAClB,WAAmB,iBACD;AAClB,QAAM,sBAAsB,CAAC,GAAG,iBAAiB,EAC9C,IAAI,CAAC,QAAQ,iBAAiB,kBAAkB,GAAG,GAAG,kBAAkB,EACxE,KAAK,IAAI;AACZ,QAAM,oBACJ,wBAAwB,iBAAiB,EAAE,KAAK,IAAI;AACtD,QAAM,UAAU,GAAG,mBAAmB;EAAK,iBAAiB;;AAC5D,QAAM,aAAa,KAAK,KAAK,MAAM,OAAO;AAC1C,QAAM,MAAM,YAAY,EAAE,WAAW,KAAK,CAAC;AAC3C,QAAM,UAAU,KAAK,KAAK,YAAY,QAAQ,GAAG,GAAG,QAAQ,KAAK,CAAC;CAAI;AACxE;AAEO,IAAM,2BAA2B,OACtC,MACA,QACA,cAAwB,CAAC,GACzB,UAAiC,aAAa,MACrB;AACzB,QAAM,oBAAoB,oBAAI,IAAY;AAE1C,SAAO,KAAK,wCAAwC;AAEpD,QAAM,QAAQ,MAAM,OAAO,wBAAwB;IACjD,KAAK;IACL,UAAU;IACV,QAAQ,CAAC,sBAAsB,GAAG,YAAY,IAAI,CAAC,QAAQ,GAAG,GAAG,KAAK,CAAC;IACvE,WAAW;EACb,CAAC;AAED,QAAM,cAAc;AACpB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,aAAa;AAClD,UAAM,QAAQ,MAAM,MAAM,GAAG,IAAI,WAAW;AAC5C,UAAM,QAAQ;MACZ,MAAM,IAAI,OAAO,SAAS;AACxB,YAAI;AACF,gBAAM,OAAO,MAAM,SAAS,MAAM,OAAO;AACzC,gBAAM,WAAW;YACf;YACA,QAAQ;UACV;AACA,qBAAW,OAAO,UAAU;AAC1B,8BAAkB,IAAI,GAAG;UAC3B;QACF,SAAS,OAAY;AACnB,iBAAO,KAAK,0BAA0B,IAAI,KAAK,MAAM,OAAO,EAAE;QAChE;MACF,CAAC;IACH;EACF;AAEA,SAAO;IACL,wBAAwB,kBAAkB,IAAI;EAChD;AAEA,SAAO;AACT;AAEO,IAAM,kCAAkC,OAC7C,MACA,QACA,aACA,YACyB;AACzB,QAAM,oBAAoB,MAAM;IAC9B;IACA;IACA;IACA;EACF;AACA,QAAM,kBAAkB,mBAAmB,IAAI;AAC/C,SAAO;AACT;AAEO,IAAM,qBAAqB,CAChC,mBACA,UACW;AACX,QAAM,wBAAwB;AAC9B,QAAM,oBAAoB,SAAS,qBAAqB;AACxD,QAAM,sBAAsB,CAAC,GAAG,iBAAiB,EAC9C;IACC,CAAC,QACC,SAAS,kBAAkB,GAAG,GAAG,sBAAsB,qBAAqB,IAAI,QAAQ,IAAI,GAAG,MAAM,EAAE;EAC3G,EACC,KAAK,IAAI;AACZ,QAAM,oBACJ,wBAAwB,iBAAiB,EAAE,KAAK,IAAI;AACtD,SAAO,GAAG,iBAAiB;EAAK,mBAAmB;EAAK,iBAAiB;;AAC3E;AAEO,IAAM,aAAa,CAAC,OAAe,GAAG,MAAM,GAAG;;;ACrP/C,IAAM,cAAc;;;AFmBpB,IAAM,iBAAiB,CAC5B,YACW;AACX,QAAM,gBAAgB,aAAa,OAAO;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,IACN,KAAK;AAAA,MACH,SAAS;AAAA,IACX;AAAA,IAEA,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,QACrC;AAAA,MACF;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,UAAUC,MAAK,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;AAAA,QACrB;AAAA,QACA,cAAc;AAAA,MAChB;AACA,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":["path","path"]}
1
+ {"version":3,"sources":["../src/index.ts","../../minifiable-keywords/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 resolveOptions,\n splitQuery,\n VIRTUAL_MODULE_ID,\n type KeywordsPluginOptions,\n type PrefixedLogger,\n} from 'minifiable-keywords';\nimport type { EnvironmentModuleGraph, Plugin, ResolvedConfig } from 'vite';\nimport { PLUGIN_NAME } from './shared';\n\nexport type { KeywordsPluginOptions } from 'minifiable-keywords';\n\nexport const keywordsPlugin = (options?: KeywordsPluginOptions): Plugin => {\n const pluginOptions = resolveOptions(options);\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 api: {\n options: pluginOptions,\n },\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 pluginOptions,\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 = pluginOptions.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(\n code,\n pluginOptions.additionalModulesToScan,\n );\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","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { parse } from '@babel/parser';\nimport _traverse, { type Node } from '@babel/traverse';\nimport { globby } from 'globby';\n\nexport const VIRTUAL_MODULE_ID = 'virtual:keywords';\nexport const RESOLVED_VIRTUAL_MODULE_ID = `\\0${VIRTUAL_MODULE_ID}`;\n\nexport interface KeywordsPluginOptions {\n additionalModulesToScan?: string[];\n isDev?: boolean;\n}\n\nexport type ResolvedKeywordsPluginOptions = Required<\n Omit<KeywordsPluginOptions, 'isDev'>\n> & { isDev?: boolean };\n\nexport const resolveOptions = (\n options?: KeywordsPluginOptions,\n): ResolvedKeywordsPluginOptions => {\n return {\n additionalModulesToScan: options?.additionalModulesToScan || [],\n isDev: options?.isDev,\n };\n};\n\nexport interface Logger {\n info: (message: string) => void;\n warn: (message: string) => void;\n error: (message: string) => void;\n}\n\nexport interface PrefixedLogger extends Logger {\n pluginName: string;\n}\n\nexport const createPrefixedLogger = (\n logger: Logger,\n pluginName: string,\n usePrefix: boolean = true,\n): PrefixedLogger => {\n const prefix = usePrefix ? `[${pluginName}] ` : '';\n const prefixed = (message: string) => `${prefix}${message}`;\n return {\n pluginName,\n info: (message: string) => logger.info(prefixed(message)),\n warn: (message: string) => logger.warn(prefixed(message)),\n error: (message: string) => logger.error(prefixed(message)),\n };\n};\n\n// ref: https://github.com/babel/babel/discussions/13093\nconst traverse =\n typeof _traverse === 'function'\n ? _traverse\n : ((_traverse as any).default as typeof _traverse);\n\nexport const extractKeywords = (\n code: string,\n additionalModulesToScan: string[] = [],\n): Set<string> => {\n const keywords = new Set<string>();\n\n // Fast-path: Skip parsing if no relevant imports are present in the code.\n const containsTargetModule =\n code.includes(VIRTUAL_MODULE_ID) ||\n additionalModulesToScan.some((moduleName) => code.includes(moduleName));\n\n if (!containsTargetModule) {\n return keywords;\n }\n\n let ast: Node;\n try {\n ast = parse(code, {\n sourceType: 'module',\n plugins: ['typescript', 'jsx'],\n errorRecovery: true,\n });\n } catch (e) {\n return keywords;\n }\n\n const keywordNamespaces = new Set<string>();\n\n traverse(ast, {\n enter(nodePath) {\n const node = nodePath.node;\n\n if (node.type === 'ImportDeclaration') {\n const isTargetModule =\n node.source.value === VIRTUAL_MODULE_ID ||\n additionalModulesToScan.includes(node.source.value);\n\n if (isTargetModule) {\n for (const specifier of node.specifiers) {\n if (specifier.type === 'ImportNamespaceSpecifier') {\n keywordNamespaces.add(specifier.local.name);\n }\n\n if (specifier.type === 'ImportDefaultSpecifier') {\n keywords.add('default');\n }\n\n if (specifier.type === 'ImportSpecifier') {\n if (specifier.imported.type === 'Identifier') {\n keywords.add(specifier.imported.name);\n }\n }\n }\n }\n }\n },\n });\n\n if (keywordNamespaces.size === 0) {\n return keywords;\n }\n\n traverse(ast, {\n enter(nodePath) {\n const node = nodePath.node;\n\n if (\n node.type === 'MemberExpression' &&\n !node.computed && // Exclude computed properties like K['xyz']\n node.object.type === 'Identifier' &&\n keywordNamespaces.has(node.object.name) &&\n node.property.type === 'Identifier'\n ) {\n keywords.add(node.property.name);\n }\n\n if (\n node.type === 'TSQualifiedName' &&\n node.left.type === 'Identifier' &&\n keywordNamespaces.has(node.left.name) &&\n node.right.type === 'Identifier'\n ) {\n keywords.add(node.right.name);\n }\n },\n });\n\n return keywords;\n};\n\nconst keywordConstPrefix = '_';\nconst createExportDeclaration = (keywords: Set<string>): string[] => {\n const aliases = [...keywords].map(\n (key) => ` ${keywordConstPrefix}${key} as ${key},`,\n );\n return [`export {`, ...aliases, `};`];\n};\n\nexport const generateTypesFile = async (\n collectedKeywords: Set<string>,\n root: string,\n dirname: string = '.keywords',\n filename: string = 'index.d.ts',\n): Promise<void> => {\n const keywordDeclarations = [...collectedKeywords]\n .map((key) => `declare const ${keywordConstPrefix}${key}: unique symbol;`)\n .join('\\n');\n const exportDeclaration =\n createExportDeclaration(collectedKeywords).join('\\n');\n const content = `${keywordDeclarations}\\n${exportDeclaration}\\n`;\n const pluginRoot = path.join(root, dirname);\n await mkdir(pluginRoot, { recursive: true });\n await writeFile(path.join(pluginRoot, filename), `${content.trim()}\\n`);\n};\n\nexport const collectKeywordsFromFiles = async (\n root: string,\n logger: PrefixedLogger,\n ignoredDirs: string[] = [],\n options?: KeywordsPluginOptions,\n): Promise<Set<string>> => {\n const resolvedOptions = resolveOptions(options);\n const collectedKeywords = new Set<string>();\n\n logger.info('Scanning project files for keywords...');\n\n const files = await globby('**/*.{js,ts,jsx,tsx}', {\n cwd: root,\n absolute: true,\n ignore: ['**/node_modules/**', ...ignoredDirs.map((dir) => `${dir}/**`)],\n gitignore: true,\n });\n\n const concurrency = 100;\n for (let i = 0; i < files.length; i += concurrency) {\n const chunk = files.slice(i, i + concurrency);\n await Promise.all(\n chunk.map(async (file) => {\n try {\n const code = await readFile(file, 'utf-8');\n const keywords = extractKeywords(\n code,\n resolvedOptions.additionalModulesToScan,\n );\n for (const key of keywords) {\n collectedKeywords.add(key);\n }\n } catch (error: any) {\n logger.warn(`Failed to process file ${file}: ${error.message}`);\n }\n }),\n );\n }\n\n logger.info(\n `Scan complete. Found ${collectedKeywords.size} unique keywords.`,\n );\n\n return collectedKeywords;\n};\n\nexport const collectKeywordsAndGenerateTypes = async (\n root: string,\n logger: PrefixedLogger,\n ignoredDirs?: string[],\n options?: KeywordsPluginOptions,\n): Promise<Set<string>> => {\n const collectedKeywords = await collectKeywordsFromFiles(\n root,\n logger,\n ignoredDirs,\n options,\n );\n await generateTypesFile(collectedKeywords, root);\n return collectedKeywords;\n};\n\nexport const generateModuleCode = (\n collectedKeywords: Set<string>,\n isDev: boolean,\n): string => {\n const symbolConstructorName = '__SYMBOL__';\n const symbolDeclaration = `const ${symbolConstructorName} = Symbol;`;\n const keywordDeclarations = [...collectedKeywords]\n .map(\n (key) =>\n `const ${keywordConstPrefix}${key} = /* @__PURE__ */ ${symbolConstructorName}(${isDev ? `'${key}'` : ''});`,\n )\n .join('\\n');\n const exportDeclaration =\n createExportDeclaration(collectedKeywords).join('\\n');\n return `${symbolDeclaration}\\n${keywordDeclarations}\\n${exportDeclaration}\\n`;\n};\n\nexport const splitQuery = (id: string) => id.split('?');\n","export const PLUGIN_NAME = 'vite-plugin-keywords';\n"],"mappings":"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","names":["path","path"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-keywords",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "A Vite plugin that provides minifiable Symbols (keywords) to use in place of string literals for aggressive minification/obfuscation.",
5
5
  "keywords": [
6
6
  "vite",
@@ -46,7 +46,7 @@
46
46
  "rimraf": "^6.0.1",
47
47
  "tsup": "^8.5.0",
48
48
  "vitest": "^4.0.0",
49
- "minifiable-keywords": "1.2.1"
49
+ "minifiable-keywords": "1.2.3"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "vite": "^6.0.0 || ^7.0.0"