Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@ignsg/vite-build-tools

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ignsg/vite-build-tools - npm Package Compare versions

Comparing version 0.0.1 to 0.1.0

dist/assets/utilities.f5a7e80b.js

21

package.json
{
"name": "@ignsg/vite-build-tools",
"version": "0.0.1",
"version": "0.1.0",
"type": "module",

@@ -20,17 +20,18 @@ "publishConfig": {

"./vite-plugin-entries": {
"types": "./dist/vite-plugin-entries.d.ts",
"import": "./dist/vite-plugin-entries.es.js",
"default": "./dist/vite-plugin-entries.es.js"
"types": "./dist/vite-plugin-entries-049bea89.d.ts",
"import": "./dist/vite-plugin-entries-049bea89.es.js",
"default": "./dist/vite-plugin-entries-049bea89.es.js"
},
"./vite-plugin-dts": {
"types": "./dist/vite-plugin-dts.d.ts",
"import": "./dist/vite-plugin-dts.es.js",
"default": "./dist/vite-plugin-dts.es.js"
"types": "./dist/vite-plugin-dts-90a5d06f.d.ts",
"import": "./dist/vite-plugin-dts-90a5d06f.es.js",
"default": "./dist/vite-plugin-dts-90a5d06f.es.js"
}
},
"devDependencies": {
"rollup": "3.2.5",
"vite": "3.2.2",
"vitest": "0.25.0"
"@workspace/root": "workspace:*",
"rollup": "3.3.0",
"vite": "3.2.3",
"vitest": "0.25.2"
}
}

@@ -5,2 +5,3 @@ import { LibraryFormats } from "vite";

| {
/** If true will mark this entry as the main entry - it can be imported using just you library's package name */
isMain: boolean;

@@ -11,2 +12,16 @@ exportPath?: never;

isMain?: never;
/** Relative path (with or without `./` prefix) that will be used as the path inside package.json's exports field and will be available for import by your library's users.
*
* For example given the default `outDir: "./dist"` and an `outputPath: "./file" would generate:
* ```json
* {
* "exports": {
* "./file": {
* "import": "./dist/file.js",
* ...
* }
* }
* }
* ```
*/
exportPath: string;

@@ -16,18 +31,60 @@ };

export interface EntryOptions {
/** The path to the source code of the entry */
sourcePath: string;
/** Relative path ending with the name of the output (without an extension) that will be generated inside of the directory specified by `outDir`.
*
* **Default: `sourcePath`**
*
* For example, given the default `outDir: "./dist"`:
* - An `outputPath: "./file"` would generate `./dist/file.js`
* - An `outputPath: "./folder/file"` would generate `./dist/folder/file.js`
*/
outputPath?: string;
/** Contains either `{ isMain: true }` or `{ exportPath: "..." }`
*
* **Default: `{ exportPath: outputPath }`**
*
* @see {@linkcode EntryOptionsDotExports.exportPath}
*/
exports?: EntryOptionsDotExports;
}
export interface Entry {
sourcePath: string;
outputPath: string;
exportPath: string;
}
export interface PluginOptions {
/** If true supresses the log messages about updating the user's package.json.
*
* **Default: `false`** */
silent?: boolean;
/** Array of package.json keys before which the plugin should place the generated entries. First match will be used.
*
* **Default: `["export", "dependencies", "devDependencies"]`**
*
* For example to generate "exports" before "scripts" you can specify ["scripts"] and the plugin will adjust the package.json to be:
* ```json
* {
* ...,
* "exports": ...,
* "scripts": ...,
* ...,
* }
* ```
*/
packageJsonInjectionPoints?: string[];
/** If provided and an entry in `entries` does not contain an explicit `outputPath` will strip this prefix when generating the output files in your output folder.
*
* For example if you have your source files in `src` and your `outDir` set to `./dist` an entry such as:
* ```json
* {
* sourcePath: "./src/index.ts"
* }
* ```
* would generate its output as `./dist/src/index.es.js`. With `sourceRoot: "./src"` it will instead generate an output as `./dist/index.es.js`.
*/
sourceRoot?: string;
/** List of formats you want to have generated */
formats: LibraryFormats[];
/** List of {@linkcode EntryOptions}'s that should be used an entry points - each represents a file/export that can be consumed by your library's user */
entries: EntryOptions[];
}
/** @private */
export interface PackageJsonExports {

@@ -40,1 +97,8 @@ [exportPath: string]: {

}
/** @private */
export interface Entry {
sourcePath: string;
outputPath: string;
exportPath: string;
}
import path from "node:path";
import { cwd } from "node:process";
import { UserConfig } from "vite";
import { ResolvedConfig, UserConfig } from "vite";
export function getPackageJSONPath(config: UserConfig) {
if (config.root) return path.join(config.root, "package.json");
import { Entry, EntryOptions, PluginOptions } from "./types.js";
return "./package.json";
export function getPackageJSONPath({ config }: { config: ResolvedConfig | UserConfig }) {
return path.join(config.root ?? cwd(), "package.json");
}
export function addRelativeDot(path: string) {
if (path.startsWith("./")) return path;
return `./${path}`;
}
export function removeRelativeDot(path: string) {
if (!path.startsWith("./")) return path;
return path.replace(/^\.\//, "");
}
/** Normalize input entry options and their paths into a standardized format
*
* sourcePath: absolute path to the entry's source file
*
* - outputPath:
* - if `entryOptions.outputPath` not provided an extensionless path based on the sourcePath relative to the root directory
* - if provided must be a relative extensionless path
*
* - exportPath:
* - if `entryOptions.exports.isMain` is true, "."
* - if `entryOptions.exports.exportPath` not provided the `./${outputPath}`
* - if provided must be a relative extensionless path
*/
export function createReduceEntryOptionsToEntries({
config,
options,
}: {
config: UserConfig;
options: PluginOptions;
}) {
return function reduceEntryOptionsToEntries(
result: Record<string, Entry>,
entryOptions: EntryOptions,
index: number,
): Record<string, Entry> {
const sourcePath = path.isAbsolute(entryOptions.sourcePath)
? entryOptions.sourcePath
: path.join(config.root ?? cwd(), entryOptions.sourcePath);
const sourcePathChunks = path.parse(sourcePath);
const outputPath =
entryOptions.outputPath ??
path.join(
path.relative(
path.join(config.root ?? cwd(), options.sourceRoot ?? ""),
sourcePathChunks.dir,
),
sourcePathChunks.name,
);
const exportPath = entryOptions.exports?.exportPath ?? outputPath;
const entry: Entry = {
sourcePath,
outputPath: removeRelativeDot(outputPath),
exportPath: entryOptions.exports?.isMain ? "." : addRelativeDot(exportPath),
};
return {
...result,
[index.toString()]: entry,
};
};
}
import * as fs from "node:fs";
import * as path from "node:path";
import { cwd } from "node:process";
import { Plugin, UserConfig } from "vite";
import { Plugin, ResolvedConfig } from "vite";
import { Entry, PackageJsonExports, PluginOptions } from "./types.js";
import { getPackageJSONPath } from "./utilities.js";
import { reduceEntryOptionsToEntries } from "./vite-plugin-entries.js";
import { createReduceEntryOptionsToEntries, getPackageJSONPath } from "./utilities.js";
export type { PluginOptions };
type InputToOutput = Map<string, string>;
interface PackageJsonTypeExports {

@@ -18,3 +20,13 @@ [exportPath: string]: {

function createReduceEntriesToPackageExports({ outDir }: { outDir?: string }) {
function createReduceEntriesToPackageExports({
inputToOutput,
buildConfig,
}: {
inputToOutput: InputToOutput;
buildConfig: ResolvedConfig["build"];
}) {
function outputToExportPath(output: string) {
return "./" + path.join(buildConfig.outDir, output);
}
return function reduceEntriesToPackageExports(

@@ -24,2 +36,8 @@ result: PackageJsonTypeExports,

): PackageJsonTypeExports {
const output = inputToOutput.get(entry.sourcePath);
if (!output) {
throw new Error(`Cannot find actual .dts output path for entry source "${entry.sourcePath}"`);
}
return {

@@ -29,3 +47,3 @@ ...result,

[entry.exportPath]: {
types: "./" + path.join(outDir ?? "", `${entry.outputPath}.d.ts`),
types: outputToExportPath(output),
},

@@ -36,5 +54,7 @@ };

function createReduceExistingExportsEntriesToTypedPackageExports(
entryTypeExports: Map<string, { types: string }>,
) {
function createReduceExistingExportsEntriesToTypedPackageExports({
entryTypeExports,
}: {
entryTypeExports: Map<string, { types: string }>;
}) {
return function reduceExistingExportsEntriesToTypedPackageExports(

@@ -62,7 +82,7 @@ result: PackageJsonExports,

export default async function dtsPlugin(opts: PluginOptions): Promise<Plugin> {
export default async function dtsPlugin(options: PluginOptions): Promise<Plugin> {
let entries: Map<string, Entry>;
let config: UserConfig;
let config: ResolvedConfig;
let bundleGenerated = false;
const inputToOutput: InputToOutput = new Map();

@@ -72,44 +92,60 @@ return {

config(userConfig) {
entries = new Map(Object.entries(opts.entries.reduce(reduceEntryOptionsToEntries, {})));
entries = new Map(
Object.entries(
options.entries.reduce(
createReduceEntryOptionsToEntries({ config: userConfig, options }),
{},
),
),
);
},
configResolved(userConfig) {
config = userConfig;
},
generateBundle(options) {
if (bundleGenerated) return;
generateBundle(outputOptions, bundle) {
if (outputOptions.format !== options.formats[0]) return;
for (const { outputPath, sourcePath } of entries.values()) {
if (!options.dir) {
throw new Error("");
}
for (const assetOrChunk of Object.values(bundle)) {
if (assetOrChunk.type !== "chunk") continue;
if (!assetOrChunk.facadeModuleId) continue;
if (!assetOrChunk.isEntry) continue;
const file = fs.readFileSync(sourcePath).toString();
const outputDir = path.parse(outputPath).dir;
const file = fs.readFileSync(assetOrChunk.facadeModuleId).toString();
const hasDefaultExport = /^(export default |export \{[^}]+? as default\s*[,}])/m.test(file);
const relativePath = path.relative(
path.resolve(path.join(options.dir, outputDir)),
path.join(path.parse(sourcePath).dir, `${path.parse(sourcePath).name}.js`),
path.join(config.root ?? cwd(), assetOrChunk.fileName),
assetOrChunk.facadeModuleId,
);
const hasDefaultExport = /^(export default |export \{[^}]+? as default\s*[,}])/m.test(file);
const relativeJsPath = relativePath.replace(/\.ts(x)?$/, ".js$1");
const source =
`export * from "${relativePath}"` +
(hasDefaultExport ? `\nexport {default} from "${relativePath}"` : ``);
`export * from "${relativeJsPath}"` +
(hasDefaultExport ? `\nexport {default} from "${relativeJsPath}"` : ``);
const output = path.join(
path.parse(assetOrChunk.fileName).dir,
`${path.parse(assetOrChunk.fileName).name.replace(/\.(es|cjs)$/, "")}.d.ts`,
);
inputToOutput.set(assetOrChunk.facadeModuleId, output);
this.emitFile({
type: "asset",
fileName: path.join(outputDir, `${path.parse(outputPath).name}.d.ts`),
fileName: output,
source,
});
}
bundleGenerated = true;
},
closeBundle() {
const packageDetails = JSON.parse(fs.readFileSync(getPackageJSONPath(config)).toString());
const packageDetails = JSON.parse(fs.readFileSync(getPackageJSONPath({ config })).toString());
const entryTypeExports = new Map(
Object.entries(
Array.from(entries.values()).reduce(
createReduceEntriesToPackageExports({ ...config.build }),
createReduceEntriesToPackageExports({ inputToOutput, buildConfig: config.build }),
{},

@@ -120,11 +156,24 @@ ),

this.warn("Adding type definitions to the `exports` field in your package.json ✅");
if (
!packageDetails["#exports"] ||
!packageDetails["#exports"].startsWith("Generated automatically")
) {
throw new Error(
`Couldn't find the auto-generated marker #exports - add the vite-plugin-entries plugin before vite-plugin-dts (it must run after it)`,
);
}
if (!options.silent)
this.warn("Adding type definitions to the `exports` field in your package.json ✅");
packageDetails.exports = Array.from(
Object.entries(packageDetails.exports as PackageJsonExports),
).reduce(createReduceExistingExportsEntriesToTypedPackageExports(entryTypeExports), {});
).reduce(createReduceExistingExportsEntriesToTypedPackageExports({ entryTypeExports }), {});
fs.writeFileSync(getPackageJSONPath(config), JSON.stringify(packageDetails, undefined, 4));
fs.writeFileSync(
getPackageJSONPath({ config }),
JSON.stringify(packageDetails, undefined, 4),
);
},
};
}
import * as fs from "node:fs";
import * as path from "node:path";
import { BuildOptions, LibraryFormats, Plugin, UserConfig } from "vite";
import { BuildOptions, LibraryFormats, Plugin, ResolvedConfig } from "vite";
import { Entry, EntryOptions, PackageJsonExports, PluginOptions } from "./types.js";
import { getPackageJSONPath } from "./utilities.js";
import { Entry, PackageJsonExports, PluginOptions } from "./types.js";
import { createReduceEntryOptionsToEntries, getPackageJSONPath } from "./utilities.js";

@@ -16,2 +16,4 @@ export type { PluginOptions };

type InputToOutput = Map<string, Partial<Record<LibraryFormats, string>>>;
function reduceEntryMapToInput(result: InputOptions, [id, entry]: [string, Entry]): InputOptions {

@@ -26,8 +28,18 @@ return {

function createReduceEntriesToPackageExports({
outDir,
formats,
config,
options,
inputToOutput,
}: {
outDir?: string;
formats: LibraryFormats[];
config: ResolvedConfig;
options: PluginOptions;
inputToOutput: InputToOutput;
}) {
function outputToExportPath(output: string | undefined, entry: Entry) {
if (!output) {
throw new Error(`Cannot find actual output path for entry source "${entry.sourcePath}"`);
}
return "./" + path.join(config.build.outDir, output);
}
return function reduceEntriesToPackageExports(

@@ -37,5 +49,10 @@ result: PackageJsonExports,

): PackageJsonExports {
const esmExport = "./" + path.join(outDir ?? "", `${entry.outputPath}.es.js`);
const cjsExport = "./" + path.join(outDir ?? "", "cjs", `${entry.outputPath}.cjs.js`);
const esmExport = options.formats.includes("es")
? outputToExportPath(inputToOutput.get(entry.sourcePath)?.es, entry)
: undefined;
const cjsExport = options.formats.includes("cjs")
? outputToExportPath(inputToOutput.get(entry.sourcePath)?.cjs, entry)
: undefined;
return {

@@ -45,8 +62,8 @@ ...result,

[entry.exportPath]: {
...(formats.includes("es") ? { import: esmExport } : undefined),
...(formats.includes("cjs") ? { require: cjsExport } : undefined),
...(esmExport ? { import: esmExport } : undefined),
...(cjsExport ? { require: cjsExport } : undefined),
...(formats[0] === "es"
...(options.formats[0] === "es"
? { default: esmExport }
: formats[0] === "cjs"
: options.formats[0] === "cjs"
? { default: cjsExport }

@@ -59,31 +76,14 @@ : undefined),

export function reduceEntryOptionsToEntries(
result: Record<string, Entry>,
entryOptions: EntryOptions,
index: number,
): Record<string, Entry> {
const outputPath = entryOptions.outputPath ?? path.parse(entryOptions.sourcePath).name;
const entry: Entry = {
sourcePath: entryOptions.sourcePath,
outputPath,
exportPath: entryOptions.exports?.isMain
? "."
: `./${entryOptions.exports?.exportPath ?? outputPath}`,
};
return {
...result,
[index.toString()]: entry,
};
}
function createMapFormatToOutputOptions(
isEsModule: boolean,
outputOptions: RollupOutputOptions,
entries: Map<string, Entry>,
) {
function createMapFormatToOutputOptions({
isEsModule,
outputConfig,
entries,
}: {
isEsModule: boolean;
outputConfig: RollupOutputOptions;
entries: Map<string, Entry>;
}) {
return function mapFormatToOutputOptions(format: LibraryFormats): RollupSingleOutputOptions {
return {
...outputOptions,
...outputConfig,
format,

@@ -101,3 +101,3 @@ interop: isEsModule && format === "cjs",

const outputFilename = `${entry.outputPath}.[format].js`;
const outputFilename = `${entry.outputPath}-[hash].[format].js`;

@@ -111,10 +111,56 @@ if (format === "cjs") return `cjs/${outputFilename}`;

export default async function entriesPlugin(opts: PluginOptions): Promise<Plugin> {
let config: UserConfig;
function verifyBuildOptions(opts: PluginOptions) {
opts.entries.forEach((entry) => {
if (entry.outputPath && path.isAbsolute(entry.outputPath)) {
throw new Error(
`entry.outputPath "${entry.outputPath}" cannot be absolute - use build.outDir to specify the output directory instead`,
);
}
if (entry.exports?.exportPath) {
if (path.isAbsolute(entry.exports.exportPath)) {
throw new Error(
`entry.exports.exportPath "${entry.exports.exportPath}" cannot be absolute - it's used inside of package.json's exports field (consult the documentation at https://nodejs.org/api/packages.html#conditional-exports)`,
);
}
if (entry.exports.exportPath === ".") {
throw new Error(
`entry.exports.exportPath "${entry.exports.exportPath}" cannot be "." - set entry.exports.isMain to true instead`,
);
}
}
});
}
function createPackageJsonModuleFields({
options,
exports,
}: {
options: PluginOptions;
exports: PackageJsonExports;
}) {
return {
module: options.formats.includes("es") ? exports["."]?.import : undefined,
main: options.formats.includes("cjs") ? exports["."]?.require : undefined,
"#exports": "Generated automatically by @ignsg/vite-build-tools",
exports,
};
}
export default async function entriesPlugin(options: PluginOptions): Promise<Plugin> {
let config: ResolvedConfig;
let entries: Map<string, Entry>;
verifyBuildOptions(options);
const inputToOutput: InputToOutput = new Map();
return {
name: "vite:entries",
config(userConfig) {
const packageDetails = JSON.parse(fs.readFileSync(getPackageJSONPath(userConfig)).toString());
const packageDetails = JSON.parse(
fs.readFileSync(getPackageJSONPath({ config: userConfig })).toString(),
);

@@ -125,3 +171,10 @@ const isEsModule = packageDetails.type === "module";

entries = new Map(Object.entries(opts.entries.reduce(reduceEntryOptionsToEntries, {})));
entries = new Map(
Object.entries(
options.entries.reduce(
createReduceEntryOptionsToEntries({ config: userConfig, options }),
{},
),
),
);

@@ -133,15 +186,29 @@ userConfig.build.rollupOptions = {

output: opts.formats.map(
createMapFormatToOutputOptions(
output: options.formats.map(
createMapFormatToOutputOptions({
isEsModule,
userConfig.build.rollupOptions?.output,
outputConfig: userConfig.build.rollupOptions?.output,
entries,
),
}),
),
};
},
configResolved(userConfig) {
config = userConfig;
},
generateBundle(outputOptions) {
generateBundle(outputOptions, bundle) {
for (const assetOrChunk of Object.values(bundle)) {
if (assetOrChunk.type !== "chunk") continue;
if (!assetOrChunk.facadeModuleId) continue;
if (!assetOrChunk.isEntry) continue;
// Gather the mapping from source file paths (facadeModuleId) to the generated output files (fileName)
inputToOutput.set(assetOrChunk.facadeModuleId, {
...inputToOutput.get(assetOrChunk.facadeModuleId),
[outputOptions.format]: assetOrChunk.fileName,
});
}
if (outputOptions.format === "cjs") {

@@ -165,34 +232,69 @@ this.emitFile({

closeBundle() {
const packageDetails = JSON.parse(fs.readFileSync(getPackageJSONPath(config)).toString());
const generatedKeys = ["#exports", "exports", "main", "module"];
const { dependencies, peerDependencies, devDependencies, ...restPackageDetails } =
packageDetails;
const injectionPoints = options.packageJsonInjectionPoints ?? [
"exports",
"dependencies",
"devDependencies",
];
delete restPackageDetails.exports;
const packageDetails = JSON.parse(fs.readFileSync(getPackageJSONPath({ config })).toString());
this.warn(
"Adding an `exports` field to your package.json based on your build configuration ✅",
);
// Protect against overwriting existing user package.json configuration
if (
!packageDetails["#exports"] ||
!packageDetails["#exports"].startsWith("Generated automatically")
) {
const conflictingKeys = generatedKeys.filter(
(generatedKey) => generatedKey in packageDetails,
);
if (conflictingKeys.length > 0) {
const serializedConflictingKeys = conflictingKeys
.map((conflict) => `"${conflict}"`)
.join(", ");
throw new Error(
`Detected that the keys: ${serializedConflictingKeys} already exist in the package.json but the auto-generated marker "#exports" does not. Aborting in order not to overwrite these keys - add an "#exports": "Generated automatically" anywhere in your package.json to confirm these keys can be overwritten`,
);
}
}
if (!options.silent)
this.warn(
"Adding an `exports` field to your package.json based on your build configuration ✅",
);
const exports = Array.from(entries.values()).reduce(
createReduceEntriesToPackageExports({ ...config.build, ...opts }),
createReduceEntriesToPackageExports({ config, options, inputToOutput }),
{},
);
let exportsGenerated = false;
let adjustedPackageJson = Object.entries(packageDetails).reduce((result, [key, value]) => {
const exportsInjection = injectionPoints.includes(key)
? createPackageJsonModuleFields({ options, exports })
: undefined;
if (exportsInjection) exportsGenerated = true;
return {
...result,
...exportsInjection,
...(generatedKeys.includes(key) ? {} : { [key]: value }),
};
}, {} as Record<string, unknown>);
if (!exportsGenerated) {
// No injection point was found, attach the exports to the end instead
adjustedPackageJson = {
...adjustedPackageJson,
...createPackageJsonModuleFields({ options, exports }),
};
}
fs.writeFileSync(
getPackageJSONPath(config),
JSON.stringify(
{
...restPackageDetails,
module: opts.formats.includes("es") ? exports["."]?.import : undefined,
main: opts.formats.includes("cjs") ? exports["."]?.require : undefined,
"#exports": "Generated automatically by @ignsg/vite-build-tools",
exports,
dependencies,
peerDependencies,
devDependencies,
},
undefined,
4,
),
getPackageJSONPath({ config }),
JSON.stringify(adjustedPackageJson, undefined, 4),
);

@@ -199,0 +301,0 @@ },

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc