modular-library 0.0.1 → 0.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 alfredo salzillo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,7 +1,21 @@
1
1
  # modular-library
2
2
 
3
+ ![npm version](https://img.shields.io/npm/v/modular-library)
4
+ ![node version](https://img.shields.io/badge/node-%3E%3D22-blue)
5
+ ![license](https://img.shields.io/github/license/alfredosalzillo/modular-library)
6
+
3
7
  `modular-library` is a utility library for generating modular libraries.
4
8
 
9
+ ## Why
10
+
11
+ Building a modular library manually is usually tedious and error-prone:
12
+
13
+ - keeping `package.json` `exports` in sync with your generated files,
14
+ - wiring and maintaining complex glob-based inputs,
15
+ - avoiding output path mistakes when your source tree grows.
16
+
17
+ `modular-library` is a **zero-config** way to generate modular outputs for `vite`, `rollup`, and `rolldown` with predictable structure.
18
+
5
19
  ## What is a modular library?
6
20
 
7
21
  A **modular library** is a library split into small, focused modules instead of one large, monolithic package.
@@ -23,6 +37,28 @@ Typical examples of modular libraries include:
23
37
 
24
38
  With `modular-library`, the goal is to make the creation of this kind of modular architecture faster and more consistent.
25
39
 
40
+ ### Why this is different from standard library mode
41
+
42
+ | Approach | Example import | What happens |
43
+ | --- | --- | --- |
44
+ | Standard library mode | `import { button } from "my-lib";` | Requires evaluating the full package entry. |
45
+ | Modular library (`modular-library`) | `import button from "my-lib/button";` | Loads only the `button` module code. |
46
+
47
+ ### Before vs. After output
48
+
49
+ ```text
50
+ # Before (standard single-entry build)
51
+ dist/
52
+ index.js
53
+
54
+ # After (modular output with modular-library)
55
+ dist/
56
+ button.js
57
+ modal.js
58
+ utils/
59
+ formatDate.js
60
+ ```
61
+
26
62
  ## Installation
27
63
 
28
64
  > **Node.js requirement:** This library supports only Node.js `22` or newer.
@@ -89,9 +125,11 @@ export default {
89
125
 
90
126
  All plugin variants support the same options:
91
127
 
92
- - `relative` (default: `"src/"`): base path removed from generated output keys.
93
- - `glob`: [`fs.globSync`](https://nodejs.org/api/fs.html#fsglobsyncpattern-options) options.
94
- - `transformOutputPath(outputPath, inputPath)`: customize each generated output path.
128
+ | Option | Type | Default | Description |
129
+ | --- | --- | --- | --- |
130
+ | `relative` | `string` | `"src/"` | Base path removed from generated output keys. |
131
+ | `glob` | [`GlobOptions`](https://nodejs.org/api/fs.html#fsglobsyncpattern-options) | `undefined` | Options forwarded to `fs.globSync`. |
132
+ | `transformOutputPath` | `(outputPath: string, inputPath: string) => string` | `undefined` | Customize each generated output path. |
95
133
 
96
134
  Example with options:
97
135
 
@@ -113,6 +151,18 @@ export default {
113
151
  };
114
152
  ```
115
153
 
154
+ ### Exports tip
155
+
156
+ To make your package consumable with subpath imports, add an `exports` map in your `package.json`:
157
+
158
+ ```json
159
+ {
160
+ "exports": {
161
+ "./*": "./dist/*.js"
162
+ }
163
+ }
164
+ ```
165
+
116
166
  ## Development
117
167
 
118
168
  ```bash
@@ -0,0 +1 @@
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`node:fs`),l=require(`node:path`);l=s(l);var u=e=>typeof e==`string`,d=e=>e.replace(/\.[^/.]+$/,``).replace(/\\/g,`/`),f={relative:`src/`},p=(e,t)=>{let n=[e].flat(),r=n.filter(e=>u(e)),i=n.filter(e=>!u(e)),a=(0,c.globSync)(r.map(e=>e.replace(/\\/g,`/`)),{...t?.glob,withFileTypes:!1}).map(e=>{let n=l.default.relative(t?.relative??f.relative,e),r=n.startsWith(`../`)?l.default.relative(`./`,e):n;return[d(t?.transformOutputPath?t.transformOutputPath(r,e):r),e]});return Object.assign({},Object.fromEntries(a),...i)};Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return p}});
@@ -0,0 +1,10 @@
1
+ import { GlobOptionsWithoutFileTypes } from 'node:fs';
2
+ export type CreateEntryInput = string | string[] | Record<string, string>;
3
+ type CreateEntriesGlobOptions = GlobOptionsWithoutFileTypes;
4
+ export type CreateEntriesOptions = {
5
+ glob?: CreateEntriesGlobOptions;
6
+ relative?: string;
7
+ transformOutputPath?: (path: string, fileName: string) => string;
8
+ };
9
+ declare const createEntries: (input: CreateEntryInput, options?: CreateEntriesOptions) => any;
10
+ export default createEntries;
@@ -0,0 +1 @@
1
+ const e=require(`../chunks/createEntries.js`);var t=`modular-library/rolldown`,n=n=>({name:t,options(t){if(!t.input)return this.warn&&this.warn(`At least one input is required`),t;let r=e.t(t.input,n);return{...t,input:r}}});module.exports=n;
@@ -0,0 +1,2 @@
1
+ export * from './plugin';
2
+ export { default } from './plugin';
@@ -0,0 +1,15 @@
1
+ import { t as e } from "../chunks/createEntries.js";
2
+ //#region src/rolldown/plugin.ts
3
+ var t = "modular-library/rolldown", n = (n) => ({
4
+ name: t,
5
+ options(t) {
6
+ if (!t.input) return this.warn && this.warn("At least one input is required"), t;
7
+ let r = e(t.input, n);
8
+ return {
9
+ ...t,
10
+ input: r
11
+ };
12
+ }
13
+ });
14
+ //#endregion
15
+ export { n as default };
@@ -0,0 +1,9 @@
1
+ import { Plugin } from 'rolldown';
2
+ import { CreateEntriesOptions } from '../createEntries';
3
+ export type RolldownModularLibraryOptions = CreateEntriesOptions;
4
+ /**
5
+ * rolldownModularLibrary is a rolldown plugin to use multiple entry points and preserve the directory
6
+ * structure in the dist folder
7
+ */
8
+ declare const rolldownModularLibrary: (options?: RolldownModularLibraryOptions) => Plugin;
9
+ export default rolldownModularLibrary;
@@ -0,0 +1 @@
1
+ const e=require(`../chunks/createEntries.js`);var t=`modular-library/rollup`,n=n=>({name:t,buildStart(){},options(t){if(!t.input)return this.warn&&this.warn(`At least one input is required`),t;let r=e.t(t.input,n);return{...t,input:r}}});module.exports=n;
@@ -0,0 +1,2 @@
1
+ export * from './plugin';
2
+ export { default } from './plugin';
@@ -0,0 +1,16 @@
1
+ import { t as e } from "../chunks/createEntries.js";
2
+ //#region src/rollup/plugin.ts
3
+ var t = "modular-library/rollup", n = (n) => ({
4
+ name: t,
5
+ buildStart() {},
6
+ options(t) {
7
+ if (!t.input) return this.warn && this.warn("At least one input is required"), t;
8
+ let r = e(t.input, n);
9
+ return {
10
+ ...t,
11
+ input: r
12
+ };
13
+ }
14
+ });
15
+ //#endregion
16
+ export { n as default };
@@ -0,0 +1,9 @@
1
+ import { Plugin } from 'rollup';
2
+ import { CreateEntriesOptions } from '../createEntries';
3
+ export type RollupModularLibraryOptions = CreateEntriesOptions;
4
+ /**
5
+ * modularLibrary is a rollup plugin to use multiple entry point and preserve the directory
6
+ * structure in the dist folder
7
+ * */
8
+ declare const rollupModularLibrary: (options?: RollupModularLibraryOptions) => Plugin;
9
+ export default rollupModularLibrary;
@@ -0,0 +1 @@
1
+ const e=require(`../chunks/createEntries.js`);var t=`modular-library/vite`,n=n=>({name:t,apply:`build`,config(t){if(!t.build?.lib)return this.warn&&this.warn(`The build.lib option is required`),t;let r=t.build?.lib?.entry;return r?(t.build.lib.entry=e.t(r,n),t):(this.warn&&this.warn(`At least one entry is required`),t)}});module.exports=n;
@@ -0,0 +1,2 @@
1
+ export * from './plugin';
2
+ export { default } from './plugin';
@@ -0,0 +1,13 @@
1
+ import { t as e } from "../chunks/createEntries.js";
2
+ //#region src/vite/plugin.ts
3
+ var t = "modular-library/vite", n = (n) => ({
4
+ name: t,
5
+ apply: "build",
6
+ config(t) {
7
+ if (!t.build?.lib) return this.warn && this.warn("The build.lib option is required"), t;
8
+ let r = t.build?.lib?.entry;
9
+ return r ? (t.build.lib.entry = e(r, n), t) : (this.warn && this.warn("At least one entry is required"), t);
10
+ }
11
+ });
12
+ //#endregion
13
+ export { n as default };
@@ -0,0 +1,9 @@
1
+ import { Plugin } from 'vite';
2
+ import { CreateEntriesOptions } from '../createEntries';
3
+ export type ViteModularLibraryOptions = CreateEntriesOptions;
4
+ /**
5
+ * viteModularLibrary is a vite plugin to use multiple entry points and preserve the directory
6
+ * structure in the dist folder
7
+ */
8
+ declare const viteModularLibrary: (options?: ViteModularLibraryOptions) => Plugin;
9
+ export default viteModularLibrary;
package/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "modular-library",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "private": false,
5
+ "files": [
6
+ "dist"
7
+ ],
5
8
  "exports": {
6
9
  "./rollup": {
7
10
  "types": "./dist/rollup/index.d.ts",
@@ -1,12 +0,0 @@
1
- # These are supported funding model platforms
2
-
3
- github: [alfredosalzillo]
4
- patreon: # Replace with a single Patreon username
5
- open_collective: # Replace with a single Open Collective username
6
- ko_fi: # Replace with a single Ko-fi username
7
- tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8
- community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9
- liberapay: # Replace with a single Liberapay username
10
- issuehunt: # Replace with a single IssueHunt username
11
- otechie: # Replace with a single Otechie username
12
- custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
@@ -1,9 +0,0 @@
1
- version: 2
2
- updates:
3
- - package-ecosystem: npm
4
- directory: "/"
5
- schedule:
6
- interval: weekly
7
- time: "04:00"
8
- open-pull-requests-limit: 10
9
- target-branch: dev
@@ -1,32 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- pull_request:
6
-
7
- jobs:
8
- test:
9
- runs-on: ubuntu-latest
10
- strategy:
11
- fail-fast: false
12
- matrix:
13
- node-version: [22, 23, 24, 25]
14
-
15
- steps:
16
- - name: Checkout repository
17
- uses: actions/checkout@v4
18
-
19
- - name: Setup Node.js
20
- uses: actions/setup-node@v4
21
- with:
22
- node-version: ${{ matrix.node-version }}
23
- cache: npm
24
-
25
- - name: Install dependencies
26
- run: npm ci
27
-
28
- - name: Build
29
- run: npm run build
30
-
31
- - name: Test
32
- run: npm test
package/AGENTS.md DELETED
@@ -1,58 +0,0 @@
1
- # AGENTS.md
2
-
3
- This file provides context and instructions for AI coding agents working on this project.
4
-
5
- ## Setup Commands
6
-
7
- - Install dependencies: `npm install`
8
- - Build the project: `npm run build`
9
- - Run tests: `npm test`
10
- - Run lint checks: `npm run lint`
11
-
12
- ## Technology Stack
13
-
14
- - **TypeScript**: Use TypeScript for all code changes. Follow the existing configuration in `tsconfig.json`.
15
- - Keep this `AGENTS.md` technology stack section up to date whenever the project tech stack changes.
16
-
17
- ## Development Workflow
18
-
19
- 1. **Keep changes minimal**: Implement the smallest safe change that resolves the issue.
20
- 2. **Build**: Before submitting, ensure the project compiles by running `npm run build`.
21
- 3. **Validate**: Run relevant tests with `npm test` when behavior changes.
22
- 4. **Code Style**: Follow the existing style and naming patterns in the codebase.
23
- 5. **GitHub Workflows**: To test workflows locally, use [`act`](https://nektosact.com/).
24
-
25
- ## Temporary Files
26
-
27
- - **Temporary Directory**: Always place temporary files in the `.ai-tmp` directory.
28
-
29
- ## Commit Message Guidelines
30
-
31
- When creating commits, use **Conventional Commits**.
32
-
33
- ### Format
34
-
35
- ```text
36
- <type>(<scope>): <description>
37
-
38
- [optional body]
39
-
40
- [optional footer(s)]
41
- ```
42
-
43
- ### Types
44
-
45
- - `feat`: A new feature (minor version update).
46
- - `fix`: A bug fix (patch version update).
47
- - `docs`: Documentation changes only.
48
- - `style`: Changes that do not affect the meaning of the code (white-space, formatting, etc).
49
- - `refactor`: A code change that neither fixes a bug nor adds a feature.
50
- - `perf`: A code change that improves performance.
51
- - `test`: Adding missing tests or correcting existing tests.
52
- - `build`: Changes that affect the build system or external dependencies.
53
- - `ci`: Changes to CI configuration files and scripts.
54
- - `chore`: Other changes that don't modify src or test files.
55
-
56
- ### Breaking Changes
57
-
58
- Breaking changes must be indicated by a `!` after the type/scope or by including `BREAKING CHANGE:` in the footer.
package/biome.json DELETED
@@ -1,40 +0,0 @@
1
- {
2
- "$schema": "https://biomejs.dev/schemas/2.4.6/schema.json",
3
- "vcs": {
4
- "enabled": true,
5
- "clientKind": "git",
6
- "useIgnoreFile": true
7
- },
8
- "files": {
9
- "ignoreUnknown": true,
10
- "includes": ["**", "!node_modules", "!.next", "!dist", "!build"]
11
- },
12
- "formatter": {
13
- "enabled": true,
14
- "indentStyle": "space",
15
- "indentWidth": 2
16
- },
17
- "javascript": {
18
- "formatter": {
19
- "quoteStyle": "double"
20
- }
21
- },
22
- "linter": {
23
- "enabled": true,
24
- "rules": {
25
- "recommended": true
26
- }
27
- },
28
- "assist": {
29
- "actions": {
30
- "source": {
31
- "organizeImports": {
32
- "level": "on",
33
- "options": {
34
- "groups": [":PACKAGE:", ":PATH:"]
35
- }
36
- }
37
- }
38
- }
39
- }
40
- }
@@ -1,59 +0,0 @@
1
- import { globSync, type GlobOptionsWithoutFileTypes } from "node:fs";
2
- import path from "node:path";
3
-
4
- const isString = (value: unknown): value is string => typeof value === "string";
5
- const outputFileName = (filePath: string) =>
6
- filePath.replace(/\.[^/.]+$/, "").replace(/\\/g, "/");
7
-
8
- export type CreateEntryInput = string | string[] | Record<string, string>;
9
- type CreateEntriesGlobOptions = GlobOptionsWithoutFileTypes;
10
-
11
- export type CreateEntriesOptions = {
12
- glob?: CreateEntriesGlobOptions;
13
- relative?: string;
14
- transformOutputPath?: (path: string, fileName: string) => string;
15
- };
16
-
17
- const defaultOptions = {
18
- relative: `src/`,
19
- };
20
- const createEntries = (
21
- input: CreateEntryInput,
22
- options?: CreateEntriesOptions,
23
- ) => {
24
- // flat to enable input to be a string or an array
25
- const inputs = [input].flat();
26
- // separate globs inputs string from others to enable input to be a mixed array too
27
- const globs = inputs.filter((value) => isString(value));
28
- const others = inputs.filter((value) => !isString(value));
29
- const normalizedGlobs = globs.map((glob) => glob.replace(/\\/g, "/"));
30
-
31
- // get files from the strings and return as entries Object
32
- const entries = globSync(normalizedGlobs, {
33
- ...options?.glob,
34
- withFileTypes: false,
35
- }).map((name) => {
36
- const filePath = path.relative(
37
- options?.relative ?? defaultOptions.relative,
38
- name,
39
- );
40
- const isRelative = !filePath.startsWith(`../`);
41
- const relativeFilePath = isRelative ? filePath : path.relative(`./`, name);
42
- return [
43
- outputFileName(
44
- options?.transformOutputPath
45
- ? options.transformOutputPath(relativeFilePath, name)
46
- : relativeFilePath,
47
- ),
48
- name,
49
- ];
50
- });
51
- return Object.assign(
52
- {},
53
- Object.fromEntries(entries),
54
- // add no globs input to the result
55
- ...others,
56
- );
57
- };
58
-
59
- export default createEntries;
@@ -1,2 +0,0 @@
1
- export * from "./plugin";
2
- export { default } from "./plugin";
@@ -1,129 +0,0 @@
1
- import type { InputOptions } from "rolldown";
2
- import modularLibrary from "./plugin";
3
-
4
- const expectedOutput = ["fixture/input1", "fixture/input2"].sort();
5
-
6
- const applyOptions = (
7
- options: InputOptions,
8
- pluginOptions?: Parameters<typeof modularLibrary>[0],
9
- ) => {
10
- const plugin = modularLibrary(pluginOptions);
11
- if (!plugin.options) {
12
- throw new Error("options hook is required");
13
- }
14
-
15
- const context = {
16
- warn: vi.fn(),
17
- };
18
-
19
- const result = plugin.options.call(context as never, options);
20
- return {
21
- result,
22
- warn: context.warn,
23
- };
24
- };
25
-
26
- describe("modular-library/rolldown", () => {
27
- it("should have name modular-library/rolldown", () => {
28
- const plugin = modularLibrary({ relative: "./test" });
29
- expect(plugin.name).toBe("modular-library/rolldown");
30
- });
31
-
32
- it("should resolve glob", () => {
33
- const { result } = applyOptions(
34
- {
35
- input: ["test/fixture/**/*.js"],
36
- },
37
- { relative: "./test" },
38
- );
39
-
40
- expect(Object.keys(result.input as Record<string, string>).sort()).toEqual(
41
- expectedOutput,
42
- );
43
- });
44
-
45
- it("should accept a simple string as input", () => {
46
- const { result } = applyOptions(
47
- {
48
- input: "test/fixture/**/*.js",
49
- },
50
- { relative: "./test" },
51
- );
52
-
53
- expect(Object.keys(result.input as Record<string, string>).sort()).toEqual(
54
- expectedOutput,
55
- );
56
- });
57
-
58
- it("should accept an array of strings as input", () => {
59
- const { result } = applyOptions(
60
- {
61
- input: ["test/fixture/**/*.js"],
62
- },
63
- { relative: "./test" },
64
- );
65
-
66
- expect(Object.keys(result.input as Record<string, string>).sort()).toEqual(
67
- expectedOutput,
68
- );
69
- });
70
-
71
- it("should remove unresolved glob", () => {
72
- const { result } = applyOptions(
73
- {
74
- input: ["test/fixture/**/*.js", "/not-found/file.js"],
75
- },
76
- { relative: "./test" },
77
- );
78
-
79
- expect(Object.keys(result.input as Record<string, string>).sort()).toEqual(
80
- expectedOutput,
81
- );
82
- });
83
-
84
- it("should resolve relative to src as default", () => {
85
- const { result: outputFilesWithNoOptions } = applyOptions({
86
- input: ["test/fixture/**/*.js"],
87
- });
88
- const { result: outputFilesWithNoRelativeOption } = applyOptions(
89
- {
90
- input: ["./test/fixture/**/*.js"],
91
- },
92
- {},
93
- );
94
-
95
- expect(
96
- Object.keys(
97
- outputFilesWithNoOptions.input as Record<string, string>,
98
- ).sort(),
99
- ).toEqual(["test/fixture/input1", "test/fixture/input2"]);
100
- expect(
101
- Object.keys(
102
- outputFilesWithNoRelativeOption.input as Record<string, string>,
103
- ).sort(),
104
- ).toEqual(["test/fixture/input1", "test/fixture/input2"]);
105
- });
106
-
107
- it("should resolve output with transformOutputPath option", () => {
108
- const { result } = applyOptions(
109
- {
110
- input: ["test/fixture/**/*.js"],
111
- },
112
- {
113
- transformOutputPath: (output) => `dest/${output.split("/").at(-1)}`,
114
- },
115
- );
116
-
117
- expect(Object.keys(result.input as Record<string, string>).sort()).toEqual([
118
- "dest/input1",
119
- "dest/input2",
120
- ]);
121
- });
122
-
123
- it("should warn when input is missing", () => {
124
- const { result, warn } = applyOptions({}, { relative: "./test" });
125
-
126
- expect(warn).toHaveBeenCalledWith("At least one input is required");
127
- expect(result).toEqual({});
128
- });
129
- });
@@ -1,34 +0,0 @@
1
- import type { Plugin } from "rolldown";
2
- import createEntries, { type CreateEntriesOptions } from "@/createEntries";
3
-
4
- const pluginName = "modular-library/rolldown";
5
-
6
- export type RolldownModularLibraryOptions = CreateEntriesOptions;
7
-
8
- /**
9
- * rolldownModularLibrary is a rolldown plugin to use multiple entry points and preserve the directory
10
- * structure in the dist folder
11
- */
12
- const rolldownModularLibrary = (
13
- options?: RolldownModularLibraryOptions,
14
- ): Plugin => {
15
- return {
16
- name: pluginName,
17
- options(conf) {
18
- if (!conf.input) {
19
- if (this.warn) {
20
- this.warn("At least one input is required");
21
- }
22
- return conf;
23
- }
24
-
25
- const input = createEntries(conf.input, options);
26
- return {
27
- ...conf,
28
- input,
29
- };
30
- },
31
- };
32
- };
33
-
34
- export default rolldownModularLibrary;
@@ -1,2 +0,0 @@
1
- export * from "./plugin";
2
- export { default } from "./plugin";
@@ -1,114 +0,0 @@
1
- import { rollup, RollupOptions } from "rollup";
2
- import importJson from "@rollup/plugin-json";
3
- import path from "node:path";
4
- import modularLibrary from "./plugin";
5
-
6
- const expectedOutput = ["fixture/input1.js", "fixture/input2.js"].sort();
7
-
8
- const externalDependencies = ["node:fs", "path"];
9
-
10
- describe.each([
11
- ["rollup 4", rollup],
12
- ])("modular-library/rollup using %s", (_, rollup) => {
13
- const generateBundle = (options: RollupOptions) =>
14
- rollup(options).then((bundle) =>
15
- bundle.generate({
16
- format: "cjs",
17
- }),
18
- );
19
-
20
- const generateOutputFileNames = (options: RollupOptions) =>
21
- generateBundle(options).then(({ output }) =>
22
- output.map((module) => module.fileName).sort(),
23
- );
24
-
25
- it("should have name modular-library/rollup", async () => {
26
- const plugin = modularLibrary({ relative: "./test" });
27
- expect("name" in plugin).toBeTruthy();
28
- expect(plugin.name).toBe("modular-library/rollup");
29
- });
30
- it("should resolve glob", async () => {
31
- const outputFiles = await generateOutputFileNames({
32
- input: ["test/fixture/**/*.js"],
33
- plugins: [modularLibrary({ relative: "./test" })],
34
- });
35
- expect(outputFiles).toEqual(expectedOutput);
36
- });
37
- it("should accept a simple string as input", async () => {
38
- const outputFiles = await generateOutputFileNames({
39
- input: "test/fixture/**/*.js",
40
- plugins: [modularLibrary({ relative: "./test" })],
41
- });
42
- expect(outputFiles).toEqual(expectedOutput);
43
- });
44
- it("should accept an array of strings as input", async () => {
45
- const outputFiles = await generateOutputFileNames({
46
- input: ["test/fixture/**/*.js"],
47
- plugins: [modularLibrary({ relative: "./test" })],
48
- });
49
- expect(outputFiles).toEqual(expectedOutput);
50
- });
51
- it("should remove unresolved glob", async () => {
52
- const outputFiles = await generateOutputFileNames({
53
- input: ["test/fixture/**/*.js", "/not-found/file.js"],
54
- plugins: [modularLibrary({ relative: "./test" })],
55
- });
56
- expect(outputFiles).toEqual(expectedOutput);
57
- });
58
- it("should preserve no string entries", async () => {
59
- const bundle = generateBundle({
60
- // @ts-expect-error
61
- input: [
62
- "test/fixture/**/*.js",
63
- {
64
- test: "path/to/test.js",
65
- },
66
- ],
67
- plugins: [modularLibrary({ relative: "./test" })],
68
- });
69
- await expect(bundle).rejects.toThrow(/^Could not resolve entry module/);
70
- });
71
- it('should resolve relative to "src" as default', async () => {
72
- const outputFilesWithNoOptions = await generateOutputFileNames({
73
- input: ["test/fixture/**/*.js"],
74
- plugins: [modularLibrary(), importJson()],
75
- external: externalDependencies,
76
- });
77
- const outputFilesWithNoRelativeOption = await generateOutputFileNames({
78
- input: ["./test/fixture/**/*.js"],
79
- plugins: [modularLibrary({}), importJson()],
80
- external: externalDependencies,
81
- });
82
- expect(outputFilesWithNoOptions).toEqual([
83
- "test/fixture/input1.js",
84
- "test/fixture/input2.js",
85
- ]);
86
- expect(outputFilesWithNoRelativeOption).toEqual([
87
- "test/fixture/input1.js",
88
- "test/fixture/input2.js",
89
- ]);
90
- });
91
- it('should resolve non relative to "relative" options path to root', async () => {
92
- const outputFiles = await generateOutputFileNames({
93
- input: ["test/fixture/**/*.js"],
94
- plugins: [modularLibrary(), importJson()],
95
- external: ["node:fs", "path"],
96
- });
97
- expect(outputFiles).toEqual([
98
- "test/fixture/input1.js",
99
- "test/fixture/input2.js",
100
- ]);
101
- });
102
- it('should resolve output to "dist" directory', async () => {
103
- const outputFiles = await generateOutputFileNames({
104
- input: ["test/fixture/**/*.js"],
105
- plugins: [
106
- modularLibrary({
107
- transformOutputPath: (output) => `dest/${path.basename(output)}`,
108
- }),
109
- ],
110
- external: ["node:fs", "path"],
111
- });
112
- expect(outputFiles).toEqual(["dest/input1.js", "dest/input2.js"]);
113
- });
114
- });
@@ -1,34 +0,0 @@
1
- import type { Plugin } from "rollup";
2
- import createEntries, { CreateEntriesOptions } from "@/createEntries";
3
-
4
- const pluginName = "modular-library/rollup";
5
-
6
- export type RollupModularLibraryOptions = CreateEntriesOptions;
7
-
8
- /**
9
- * modularLibrary is a rollup plugin to use multiple entry point and preserve the directory
10
- * structure in the dist folder
11
- * */
12
- const rollupModularLibrary = (
13
- options?: RollupModularLibraryOptions,
14
- ): Plugin => {
15
- return {
16
- name: pluginName,
17
- buildStart() {},
18
- options(conf) {
19
- if (!conf.input) {
20
- if (this.warn) {
21
- this.warn("At least one input is required");
22
- }
23
- return conf;
24
- }
25
- const input = createEntries(conf.input, options);
26
- return {
27
- ...conf,
28
- input,
29
- };
30
- },
31
- };
32
- };
33
-
34
- export default rollupModularLibrary;
package/src/vite/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export * from "./plugin";
2
- export { default } from "./plugin";
@@ -1,192 +0,0 @@
1
- import type { UserConfig } from "vite";
2
- import modularLibrary from "./plugin";
3
-
4
- const expectedOutput = ["fixture/input1", "fixture/input2"].sort();
5
-
6
- const applyConfig = (
7
- config: UserConfig,
8
- pluginOptions?: Parameters<typeof modularLibrary>[0],
9
- ) => {
10
- const plugin = modularLibrary(pluginOptions);
11
- if (!plugin.config) {
12
- throw new Error("config hook is required");
13
- }
14
-
15
- const context = {
16
- warn: vi.fn(),
17
- };
18
-
19
- // @ts-expect-error
20
- const result = plugin.config.call(context as never, config);
21
- return {
22
- result,
23
- warn: context.warn,
24
- };
25
- };
26
-
27
- describe("modular-library/vite", () => {
28
- it("should have name modular-library/vite", () => {
29
- const plugin = modularLibrary({ relative: "./test" });
30
- expect(plugin.name).toBe("modular-library/vite");
31
- expect(plugin.apply).toBe("build");
32
- });
33
-
34
- it("should warn when build.lib is missing", () => {
35
- const { result, warn } = applyConfig({});
36
-
37
- expect(warn).toHaveBeenCalledWith("The build.lib option is required");
38
- expect(result).toEqual({});
39
- });
40
-
41
- it("should warn when build.lib.entry is missing", () => {
42
- const { result, warn } = applyConfig({
43
- build: {
44
- // @ts-expect-error
45
- lib: {},
46
- },
47
- });
48
-
49
- expect(warn).toHaveBeenCalledWith("At least one entry is required");
50
- expect(result).toEqual({
51
- build: {
52
- lib: {},
53
- },
54
- });
55
- });
56
-
57
- it("should resolve glob", () => {
58
- const { result } = applyConfig(
59
- {
60
- build: {
61
- lib: {
62
- entry: ["test/fixture/**/*.js"],
63
- },
64
- },
65
- },
66
- { relative: "./test" },
67
- );
68
-
69
- expect(
70
- Object.keys(
71
- (result.build?.lib as { entry: Record<string, string> }).entry,
72
- ).sort(),
73
- ).toEqual(expectedOutput);
74
- });
75
-
76
- it("should accept a simple string as input", () => {
77
- const { result } = applyConfig(
78
- {
79
- build: {
80
- lib: {
81
- entry: "test/fixture/**/*.js",
82
- },
83
- },
84
- },
85
- { relative: "./test" },
86
- );
87
-
88
- expect(
89
- Object.keys(
90
- (result.build?.lib as { entry: Record<string, string> }).entry,
91
- ).sort(),
92
- ).toEqual(expectedOutput);
93
- });
94
-
95
- it("should accept an array of strings as input", () => {
96
- const { result } = applyConfig(
97
- {
98
- build: {
99
- lib: {
100
- entry: ["test/fixture/**/*.js"],
101
- },
102
- },
103
- },
104
- { relative: "./test" },
105
- );
106
-
107
- expect(
108
- Object.keys(
109
- (result.build?.lib as { entry: Record<string, string> }).entry,
110
- ).sort(),
111
- ).toEqual(expectedOutput);
112
- });
113
-
114
- it("should remove unresolved glob", () => {
115
- const { result } = applyConfig(
116
- {
117
- build: {
118
- lib: {
119
- entry: ["test/fixture/**/*.js", "/not-found/file.js"],
120
- },
121
- },
122
- },
123
- { relative: "./test" },
124
- );
125
-
126
- expect(
127
- Object.keys(
128
- (result.build?.lib as { entry: Record<string, string> }).entry,
129
- ).sort(),
130
- ).toEqual(expectedOutput);
131
- });
132
-
133
- it("should resolve relative to src as default", () => {
134
- const { result: outputFilesWithNoOptions } = applyConfig({
135
- build: {
136
- lib: {
137
- entry: ["test/fixture/**/*.js"],
138
- },
139
- },
140
- });
141
- const { result: outputFilesWithNoRelativeOption } = applyConfig(
142
- {
143
- build: {
144
- lib: {
145
- entry: ["./test/fixture/**/*.js"],
146
- },
147
- },
148
- },
149
- {},
150
- );
151
-
152
- expect(
153
- Object.keys(
154
- (
155
- outputFilesWithNoOptions.build?.lib as {
156
- entry: Record<string, string>;
157
- }
158
- ).entry,
159
- ).sort(),
160
- ).toEqual(["test/fixture/input1", "test/fixture/input2"]);
161
- expect(
162
- Object.keys(
163
- (
164
- outputFilesWithNoRelativeOption.build?.lib as {
165
- entry: Record<string, string>;
166
- }
167
- ).entry,
168
- ).sort(),
169
- ).toEqual(["test/fixture/input1", "test/fixture/input2"]);
170
- });
171
-
172
- it("should resolve output with transformOutputPath option", () => {
173
- const { result } = applyConfig(
174
- {
175
- build: {
176
- lib: {
177
- entry: ["test/fixture/**/*.js"],
178
- },
179
- },
180
- },
181
- {
182
- transformOutputPath: (output) => `dest/${output.split("/").at(-1)}`,
183
- },
184
- );
185
-
186
- expect(
187
- Object.keys(
188
- (result.build?.lib as { entry: Record<string, string> }).entry,
189
- ).sort(),
190
- ).toEqual(["dest/input1", "dest/input2"]);
191
- });
192
- });
@@ -1,40 +0,0 @@
1
- import type { Plugin } from "vite";
2
- import createEntries, { type CreateEntriesOptions } from "@/createEntries";
3
-
4
- const pluginName = "modular-library/vite";
5
-
6
- export type ViteModularLibraryOptions = CreateEntriesOptions;
7
-
8
- /**
9
- * viteModularLibrary is a vite plugin to use multiple entry points and preserve the directory
10
- * structure in the dist folder
11
- */
12
- const viteModularLibrary = (options?: ViteModularLibraryOptions): Plugin => {
13
- return {
14
- name: pluginName,
15
- apply: "build",
16
- config(config) {
17
- if (!config.build?.lib) {
18
- if (this.warn) {
19
- this.warn("The build.lib option is required");
20
- }
21
- return config;
22
- }
23
- const entry = config.build?.lib?.entry;
24
-
25
- if (!entry) {
26
- if (this.warn) {
27
- this.warn("At least one entry is required");
28
- }
29
-
30
- return config;
31
- }
32
-
33
- config.build.lib.entry = createEntries(entry, options);
34
-
35
- return config;
36
- },
37
- };
38
- };
39
-
40
- export default viteModularLibrary;
@@ -1,2 +0,0 @@
1
- // eslint-disable-next-line import/prefer-default-export
2
- export function doNothing() {}
@@ -1,2 +0,0 @@
1
- // eslint-disable-next-line import/prefer-default-export
2
- export function doNothing() {}
package/tsconfig.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "CommonJS",
5
- "moduleResolution": "bundler",
6
- "declaration": true,
7
- "outDir": "dist",
8
- "rootDir": "src",
9
- "strict": true,
10
- "esModuleInterop": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "skipLibCheck": true,
13
- "baseUrl": ".",
14
- "paths": {
15
- "@/*": ["src/*"]
16
- }
17
- },
18
- "include": ["src", "node_modules/vitest/globals.d.ts"]
19
- }
package/vite.config.ts DELETED
@@ -1,36 +0,0 @@
1
- import react from "@vitejs/plugin-react";
2
- import dts from "unplugin-dts/vite";
3
- import { defineConfig } from "vite";
4
- import { resolve } from "node:path";
5
-
6
- export default defineConfig({
7
- plugins: [
8
- react(),
9
- dts({
10
- copyDtsFiles: true,
11
- outDirs: ["dist"],
12
- exclude: ["src/**/*.test.ts"],
13
- beforeWriteFile: (filePath, content) => ({
14
- filePath: filePath.replace(/([\\/])dist\1src\1/, "$1dist$1"),
15
- content,
16
- }),
17
- }),
18
- ],
19
- build: {
20
- lib: {
21
- entry: {
22
- "rolldown/index": resolve(__dirname, "src/rolldown/index.ts"),
23
- "rollup/index": resolve(__dirname, "src/rollup/index.ts"),
24
- "vite/index": resolve(__dirname, "src/vite/index.ts"),
25
- },
26
- formats: ["es", "cjs"],
27
- fileName: (format, entryName) => `${entryName}.${format}.js`,
28
- },
29
- rolldownOptions: {
30
- output: {
31
- chunkFileNames: "chunks/[name].js",
32
- },
33
- external: ["node:path", "node:fs"],
34
- },
35
- },
36
- });
package/vitest.config.ts DELETED
@@ -1,18 +0,0 @@
1
- import { defineConfig } from "vitest/config";
2
- import { resolve } from "node:path";
3
-
4
- export default defineConfig({
5
- resolve: {
6
- alias: {
7
- "@": resolve(__dirname, "src"),
8
- },
9
- },
10
- test: {
11
- environment: "node",
12
- globals: true,
13
- coverage: {
14
- provider: "v8",
15
- reportsDirectory: "./coverage",
16
- },
17
- },
18
- });