{"_id":"@accup/vite-plugin-hogen","_rev":"3-f35cf0c41775c0f64b7adeb9f4573c83","name":"@accup/vite-plugin-hogen","dist-tags":{"latest":"0.1.0"},"versions":{"0.0.1":{"name":"@accup/vite-plugin-hogen","version":"0.0.1","keywords":["dynamic asset","meta programming","vite","vite-plugin"],"author":"Yuki Fukadai","_id":"@accup/vite-plugin-hogen@0.0.1","maintainers":[{"name":"accup","email":"dev.accup@gmail.com"}],"dist":{"shasum":"b5996d67d4cd506d3f7423c78d9db2ac6d9ae485","tarball":"https://registry.npmjs.org/@accup/vite-plugin-hogen/-/vite-plugin-hogen-0.0.1.tgz","fileCount":51,"integrity":"sha512-sKyAlTeT0ui5vb4qJz/9dK13Czs7NPd4vYQy2EJqpVmqqWw0+BU3LgGfsR0DG3CyQ5f30VVFezbXX3UYtB0Sqg==","signatures":[{"sig":"MEYCIQCCrIpPcOtByvg9Vm1T4yBLQqxA5WOJtXvonolQ7ycMsAIhANx/dUmonEBt/8D3zkX+FIFDYFPgKS6FGq5rJy26EHb3","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":42482},"main":"./dist/index.js","type":"module","types":"./types/main/index.d.ts","exports":{".":{"types":"./types/main/index.d.ts","import":"./dist/index.js"},"./config":{"types":"./types/config/index.d.ts","import":"./dist/config.js"}},"scripts":{"test":"vitest","build":"vite build && tsc","typecheck":"tsc --noEmit"},"_npmUser":{"name":"accup","email":"dev.accup@gmail.com"},"description":"Vite plugin for emitting assets from TypeScript files matched by user-defined rules.","directories":{},"_nodeVersion":"26.0.0","_hasShrinkwrap":false,"peerDependencies":{"vite":"^8.0.0"},"_npmOperationalInternal":{"tmp":"tmp/vite-plugin-hogen_0.0.1_1779385384813_0.9732337899761141","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"@accup/vite-plugin-hogen","version":"0.0.2","keywords":["dynamic asset","meta programming","vite","vite-plugin"],"author":"Yuki Fukadai","license":"MIT","_id":"@accup/vite-plugin-hogen@0.0.2","maintainers":[{"name":"accup","email":"dev.accup@gmail.com"}],"dist":{"shasum":"ff99f61b2a020b8fe043bd429f83142bdee418b6","tarball":"https://registry.npmjs.org/@accup/vite-plugin-hogen/-/vite-plugin-hogen-0.0.2.tgz","fileCount":51,"integrity":"sha512-zi7aPc6SvVG0ye7TqGdDP/Lj2UYcLoARYgoRQKI6zxXYiGcTRZljBtA/m5zB/eVD/Ue27/+wvijl+s8EMbOZsQ==","signatures":[{"sig":"MEUCIC+jHkhNqkd2vLzBxGAdCWIRrpS3oT0lMWZOLQOv1OBfAiEA7+Ot3lWTxbtkOzHcFQUtZr8sf5Ewnwc2pfl//4Bh8KI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":42502},"main":"./dist/index.js","type":"module","types":"./types/main/index.d.ts","exports":{".":{"types":"./types/main/index.d.ts","import":"./dist/index.js"},"./config":{"types":"./types/config/index.d.ts","import":"./dist/config.js"}},"scripts":{"test":"vitest","build":"vite build && tsc","typecheck":"tsc --noEmit"},"_npmUser":{"name":"accup","email":"dev.accup@gmail.com"},"description":"Vite plugin for emitting assets from TypeScript files matched by user-defined rules.","directories":{},"_nodeVersion":"26.0.0","_hasShrinkwrap":false,"peerDependencies":{"vite":"^8.0.0"},"_npmOperationalInternal":{"tmp":"tmp/vite-plugin-hogen_0.0.2_1779385549993_0.8875722212727077","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"_id":"@accup/vite-plugin-hogen@0.1.0","dist":{"shasum":"e3a9f911ae780960f1599a05b7c4573bb3e1d25c","tarball":"https://registry.npmjs.org/@accup/vite-plugin-hogen/-/vite-plugin-hogen-0.1.0.tgz","integrity":"sha512-btuXYTmGPhrVycgFyRga0DtFEaZ74JE9IgHQNQjQge8vxTRub/0Xb/gTQNQOtfc04Vukk6qDXvWBJzQEmAua3Q==","fileCount":55,"unpackedSize":54666,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDgDbY2wW9CPFJPVetcEdpjNgZsYE1xUPmbdMjO5BKALgIgZx1wVC/SzYH21LzeIcg+7NuVsisu3hm7YNgzmNcm3z8="}]},"main":"./dist/index.js","name":"@accup/vite-plugin-hogen","type":"module","_from":"file:accup-vite-plugin-hogen-0.1.0.tgz","types":"./types/main/index.d.ts","author":{"name":"Yuki Fukadai"},"exports":{".":{"types":"./types/main/index.d.ts","import":"./dist/index.js"},"./config":{"types":"./types/config/index.d.ts","import":"./dist/config.js"}},"license":"MIT","scripts":{"build":"vite build && tsc","typecheck":"tsc --noEmit"},"version":"0.1.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:669ca98f-75cf-4f3b-b881-6f2fe74ae226"},"approver":{"name":"accup","email":"dev.accup@gmail.com"}},"keywords":["dynamic asset","meta programming","vite","vite-plugin"],"_resolved":"/home/runner/work/cd.vite-plugin-hogen/cd.vite-plugin-hogen/accup-vite-plugin-hogen-0.1.0.tgz","_integrity":"sha512-btuXYTmGPhrVycgFyRga0DtFEaZ74JE9IgHQNQjQge8vxTRub/0Xb/gTQNQOtfc04Vukk6qDXvWBJzQEmAua3Q==","_npmVersion":"11.16.0","description":"Vite plugin for emitting assets from TypeScript files matched by user-defined rules.","directories":{},"maintainers":[{"name":"accup","email":"dev.accup@gmail.com"}],"_nodeVersion":"24.16.0","peerDependencies":{"vite":"^8.0.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/vite-plugin-hogen_0.1.0_1780128940295_0.5514580704450329"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-21T17:43:04.727Z","modified":"2026-05-30T08:15:40.488Z","0.0.1":"2026-05-21T17:43:04.961Z","0.0.2":"2026-05-21T17:45:50.245Z","0.1.0":"2026-05-30T08:15:40.382Z"},"author":{"name":"Yuki Fukadai"},"license":"MIT","keywords":["dynamic asset","meta programming","vite","vite-plugin"],"description":"Vite plugin for emitting assets from TypeScript files matched by user-defined rules.","maintainers":[{"name":"accup","email":"dev.accup@gmail.com"}],"readme":"# @accup/vite-plugin-hogen\n\nVite plugin for emitting assets from TypeScript files matched by user-defined rules.\n\nFor each matching file, the plugin evaluates the source with Vite's ModuleRunner, lets the rule emit assets and produce the final exports, and writes the result back as the file's compiled JS.\n\n## Installation\n\n```bash\nnpm install --save-dev @accup/vite-plugin-hogen\n```\n\n## Plugin setup\n\nRegister the plugin in `vite.config.ts` with the rules to apply.\n\n```ts\nimport { hogen } from \"@accup/vite-plugin-hogen/config\";\nimport { defineConfig } from \"vite\";\n\nexport default defineConfig({\n  plugins: [\n    hogen({\n      rules: [\n        /* see below */\n      ],\n    }),\n  ],\n});\n```\n\n### Plugin options\n\n- `rules`\n\n  Rules registered with the plugin.\n\n- `assetLeakPolicy`\n\n  Policy for the check that detects a Vite asset placeholder appearing in an emitted text asset's content. Values are `\"error\"`, `\"warn\"`, and `\"ignore\"`. Defaults to `\"error\"`.\n\n## Rule\n\nA rule selects evaluation entries by id and decides how the matching file emits assets and produces exports.\n\n```ts\nimport type { HogenRule } from \"@accup/vite-plugin-hogen/config\";\n\nconst rule: HogenRule = {\n  name: \"my-rule\",\n  testEntry: (id) => /\\.my\\.ts(\\?.*)?$/u.test(id),\n\n  // Adapter hook, emits during evaluation.\n  adapterKey,\n  createAdapter,\n\n  // Evaluated-module hook, emits after evaluation.\n  processModule,\n};\n```\n\nBoth hooks are optional. `adapterKey` and `createAdapter` are set together when the adapter hook is used.\n\n### Evaluation-only rule\n\nA rule may declare no hook at all. The plugin then evaluates the source with ModuleRunner and serializes the resulting exports as static JS. A matching file becomes a build-time computation whose result is compiled into the output bundle without any asset emit.\n\n```ts\nimport type { HogenRule } from \"@accup/vite-plugin-hogen/config\";\n\nexport function createConstRule(): HogenRule {\n  return {\n    name: \"const\",\n    testEntry: (id) => /\\.const\\.ts(\\?.*)?$/u.test(id),\n  };\n}\n```\n\nA matching file uses Node APIs at build time and produces plain values.\n\n```ts\n// version.const.ts\nimport { readFileSync } from \"node:fs\";\n\nconst pkg: { version: string } = JSON.parse(\n  readFileSync(new URL(\"../package.json\", import.meta.url), \"utf-8\"),\n);\n\nexport const version = pkg.version;\n```\n\nThe compiled module is plain JS with the resolved value.\n\n```ts\nexport const version = \"1.2.3\";\n```\n\n### Adapter-based rule\n\nUse the adapter hook when each emit happens during the file's evaluation. The file imports helpers that read the adapter and call `adapter.emit` per call site. Each helper returns the URL accessors of the asset it emitted.\n\nDeclare the adapter interface and a key.\n\n```ts\nimport type { HogenAdapter } from \"@accup/vite-plugin-hogen/config\";\nimport type { HogenEmittedAsset } from \"@accup/vite-plugin-hogen/config\";\nimport { createAdapterKey } from \"@accup/vite-plugin-hogen/config\";\n\nexport interface SnapshotAdapter extends HogenAdapter {\n  /** Emit a text snapshot and return its URL accessors */\n  readonly snapshot: (text: string) => HogenEmittedAsset;\n}\n\nexport const ADAPTER_KEY = createAdapterKey<SnapshotAdapter>(\"__snapshot_adapter__\");\n```\n\nBuild the rule.\n\n```ts\nimport type { HogenRule } from \"@accup/vite-plugin-hogen/config\";\n\nexport function createSnapshotRule(): HogenRule<SnapshotAdapter> {\n  return {\n    name: \"snapshot\",\n    testEntry: (id) => /\\.snapshot\\.ts(\\?.*)?$/u.test(id),\n    adapterKey: ADAPTER_KEY,\n    createAdapter: (context) => ({\n      snapshot(text) {\n        return context.emit({\n          type: \"asset\",\n          name: \"snapshot\",\n          source: text,\n          mimeType: \"text/plain\",\n        });\n      },\n    }),\n  };\n}\n```\n\nUser code reaches the adapter through `readAdapter`.\n\n```ts\nimport { readAdapter } from \"@accup/vite-plugin-hogen\";\nimport type { HogenEmittedAsset } from \"@accup/vite-plugin-hogen\";\n\nimport { ADAPTER_KEY } from \"./adapter\";\n\nexport function snapshot(text: string): HogenEmittedAsset {\n  return readAdapter(ADAPTER_KEY).snapshot(text);\n}\n```\n\nA matching file emits one asset per call. Each helper return value carries the URL accessors of the emitted asset, so each named export has the `HogenEmittedAsset` type at both the source and consumer side.\n\n```ts\n// notes.snapshot.ts\nimport { snapshot } from \"./snapshot\";\n\nexport const intro = snapshot(\"Hello, world.\");\nexport const detail = snapshot(\"Longer payload.\");\n```\n\nA consumer picks the accessor that matches the embedding context.\n\n```ts\nimport { intro } from \"./notes.snapshot\";\n\nconst href = intro.absolutePath;\n```\n\n### Evaluated-module-based rule\n\nUse `processModule` when the rule reads the file's evaluated exports and decides what to emit. Helpers the source code imports brand each value; `processModule` detects the brand after evaluation and replaces every branded value with a URL.\n\nEach named export keeps the same name across the source and the compiled output, so the source-side and consumer-side types stay aligned. The branding helper declares the return type that consumers receive, while its runtime value carries the data the rule needs.\n\nDeclare the value shape, the brand, and the helpers.\n\n```ts\nconst REPORT_KEY = \"__report__\";\n\nexport interface Report {\n  readonly title: string;\n  readonly total: number;\n}\n\n/**\n * Brand a report value so the rule can find it among the evaluated exports.\n *\n * The runtime value carries the report data, while the declared return type matches the URL the rule writes into the compiled exports.\n *\n * @param report report data\n * @returns URL of the emitted report\n */\nexport function defineReport(report: Report): string {\n  Object.defineProperty(report, REPORT_KEY, {\n    value: true,\n    enumerable: false,\n    configurable: false,\n    writable: false,\n  });\n  // The runtime value is replaced by a URL in the rule's processModule.\n  // oxlint-disable-next-line typescript/no-unsafe-type-assertion\n  return report as unknown as string;\n}\n\nexport function isReport(value: unknown): value is Report {\n  if (typeof value !== \"object\" || value == null) {\n    return false;\n  }\n  return REPORT_KEY in value;\n}\n```\n\nBuild the rule.\n\n```ts\nimport type { HogenRule } from \"@accup/vite-plugin-hogen/config\";\n\nimport { isReport } from \"./report\";\n\nexport function createReportRule(): HogenRule {\n  return {\n    name: \"report\",\n    testEntry: (id) => /\\.report\\.ts(\\?.*)?$/u.test(id),\n    processModule(module) {\n      const replaced = Object.entries(module.exports).map(([name, value]) => {\n        if (!isReport(value)) {\n          return [name, value] as const;\n        }\n\n        const url = module.emit({\n          type: \"asset\",\n          name,\n          source: JSON.stringify(value),\n          mimeType: \"application/json\",\n        }).absolutePath;\n        return [name, url] as const;\n      });\n\n      return Object.fromEntries(replaced);\n    },\n  };\n}\n```\n\nA matching file calls the helper.\n\n```ts\n// sales.report.ts\nimport { defineReport } from \"./report\";\n\nexport const sales = defineReport({\n  title: \"Sales 2024\",\n  total: 12345,\n});\n```\n\nConsumers see the declared return type of the helper, which matches the URL the rule writes into the compiled exports.\n\n```ts\nimport { sales } from \"./sales.report\";\n// sales has type `string` and is the URL of the emitted JSON at runtime\n```\n\n`processModule` may also return exports without calling `module.emit`. The rule then only transforms values; no asset reaches the output.\n\n### Mixed rule\n\nA rule can declare both hooks. `createAdapter` exposes per-call helpers that emit during evaluation; `processModule` reads the resulting exports and emits or rewrites values that need post-evaluation handling. The two styles compose inside one file, and every export keeps the declared return type at the source and consumer side as long as each helper aligns its return type with the value the rule writes into the compiled exports.\n\n```ts\nimport type { HogenRule } from \"@accup/vite-plugin-hogen/config\";\n\nimport { isReport } from \"./report\";\n\nexport function createCatalogRule(): HogenRule<CatalogAdapter> {\n  return {\n    name: \"catalog\",\n    testEntry: (id) => /\\.catalog\\.ts(\\?.*)?$/u.test(id),\n    adapterKey: ADAPTER_KEY,\n    createAdapter: (context) => ({\n      attach(input) {\n        return context.emit({\n          type: \"asset\",\n          name: input.name,\n          source: input.source,\n          mimeType: input.mimeType,\n        });\n      },\n    }),\n    processModule(module) {\n      const replaced = Object.entries(module.exports).map(([name, value]) => {\n        if (!isReport(value)) {\n          return [name, value] as const;\n        }\n\n        const url = module.emit({\n          type: \"asset\",\n          name,\n          source: JSON.stringify(value),\n          mimeType: \"application/json\",\n        }).absolutePath;\n        return [name, url] as const;\n      });\n\n      return Object.fromEntries(replaced);\n    },\n  };\n}\n```\n\nA matching file mixes both emit styles. `attach` emits during evaluation and returns the URL accessors directly; `defineReport` brands the value, and `processModule` rewrites it into a URL after evaluation.\n\n```ts\n// store.catalog.ts\nimport { attach } from \"./attach\";\nimport { defineReport } from \"./report\";\n\nexport const logo = attach({\n  name: \"logo\",\n  source: \"<svg>...</svg>\",\n  mimeType: \"image/svg+xml\",\n});\n\nexport const sales = defineReport({\n  title: \"Sales 2024\",\n  total: 12345,\n});\n```\n\n### Rule nesting\n\nA file matched by one rule can import a file matched by another rule. The inner ModuleRunner that evaluates the entry hands a cross-rule id back to the main plugin so the main plugin processes it with the outer Rollup context, and the entry's evaluation receives the imported module's compiled exports.\n\n```ts\n// welcome.snap.ts\nimport { snapshot } from \"./snapshot\";\n\nexport const greeting = snapshot(\"Welcome.\");\n```\n\n```ts\n// page.bundle.ts\nimport { defineBundle } from \"./bundle\";\nimport * as welcome from \"./welcome.snap.ts\";\n\nexport const home = defineBundle({\n  title: \"Home\",\n  items: [{ label: \"greeting\", href: welcome.greeting.absolutePath }],\n});\n```\n\n## Module evaluation\n\nThe plugin evaluates each id at most once per session. After the first transform of a matched file, subsequent imports of the same id reuse the cached transform result, so emits and `processModule` run exactly once per session.\n\nIn dev mode, modifying a matched file invalidates its cached entry and every cached entry that reached it through a cross-rule import. Every id reached through the cross-rule delegate path is added to Vite's watcher, so a change to a file imported only by another evaluated file still triggers HMR.\n\n## Emit input\n\n`emit` accepts two input shapes.\n\n- `type: \"file\"`\n\n  Fixed-path asset. The asset is written under the given `fileName`. Set `rule.testFile` to the same path predicate so the dev server serves the file at that path and forces a full reload when the source module changes.\n\n- `type: \"asset\"`\n\n  Dynamic-path asset. The output path is derived from `name` and a content hash.\n\n`source` is a string or a `Uint8Array`. `mimeType` is read by the dev middleware to set the response Content-Type and is ignored at build time.\n\n## Emit result\n\n`emit` returns a `HogenEmittedAsset` carrying three URL accessors.\n\n- `absolutePath`\n\n  Absolute path including Vite's `base` prefix. Use this when embedding the URL inside another asset's content.\n\n- `relativePath`\n\n  Path relative to the bundle output root.\n\n- `assetRef`\n\n  Value safe to embed in a JS chunk. At build time this is a Vite asset placeholder that Vite resolves to the final URL during bundling.\n\nEmbedding `assetRef` inside another asset's content leaks the unresolved placeholder into the output. The plugin checks every text-typed `type: \"asset\"` emit for this pattern and reports a match according to the `assetLeakPolicy` option.\n","readmeFilename":"README.md"}