Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f5de8797c | |||
| aea86bd8f5 | |||
| ad5e0c1afc | |||
| cb5d827420 | |||
| 5a5f6c6944 | |||
| 54ac7e7188 | |||
| 5f0507b2c7 | |||
| ecfd7600e2 | |||
| 9cf173293d | |||
| 3a53fffe3d | |||
| 8f129527d9 | |||
| 5d9052018e | |||
| 12c5655251 | |||
| 801cab9001 | |||
| 971cb685a7 | |||
| 0900d1a605 | |||
| f0fb99c8ae | |||
| 6d88adcd1e | |||
| 4349571112 | |||
| b3080023ab | |||
| 8b6ae043a2 | |||
| 54f84ba114 | |||
| e40d6477fd |
@@ -5,7 +5,7 @@
|
||||
"githost": "gitlab.com",
|
||||
"gitscope": "gitzone",
|
||||
"gitrepo": "tsbundle",
|
||||
"description": "a bundler using rollup for painless bundling of web projects",
|
||||
"description": "a multi-bundler tool supporting esbuild, rolldown, and rspack for painless bundling of web projects",
|
||||
"npmPackagename": "@git.zone/tsbundle",
|
||||
"license": "MIT",
|
||||
"projectDomain": "git.zone"
|
||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": ["/npmextra.json"],
|
||||
"fileMatch": ["/smartconfig.json"],
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
76
changelog.md
76
changelog.md
@@ -1,5 +1,81 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-03-24 - 2.10.0 - feat(config)
|
||||
migrate project configuration to smartconfig.json and update bundler dependencies
|
||||
|
||||
- replace npmextra.json with .smartconfig.json and update CLI, init, and custom bundle loading references
|
||||
- rename the smartconfig plugin export and load tsbundle configuration through the new config file
|
||||
- update rolldown output settings to use codeSplitting: false instead of the deprecated inlineDynamicImports option
|
||||
- refresh core build and bundler dependencies including rolldown, rspack, esbuild, TypeScript, and smartfs
|
||||
- revise README documentation to reflect smartconfig.json usage and current bundler capabilities
|
||||
|
||||
## 2026-03-24 - 2.9.3 - fix(config)
|
||||
migrate configuration loading and init output from npmextra.json to smartconfig.json
|
||||
|
||||
- replace @push.rocks/npmextra with @push.rocks/smartconfig
|
||||
- load project configuration via Smartconfig in the custom module
|
||||
- update init wizard to read from and write to smartconfig.json and adjust user-facing messages accordingly
|
||||
|
||||
## 2026-03-11 - 2.9.2 - fix(mod_esbuild)
|
||||
preserve function and class names in esbuild output by enabling keepNames in both dev and prod configs
|
||||
|
||||
- Added keepNames: true to esbuild options in ts/mod_esbuild/index.child.ts for the non-minified/dev build
|
||||
- Added keepNames: true to esbuild options in ts/mod_esbuild/index.child.ts for the minified/production build to improve stack traces, debugging, and runtime reflection
|
||||
|
||||
## 2026-03-05 - 2.9.1 - fix(mod_custom)
|
||||
use absolute smartfs entry.path instead of joining with dirPath when building fullPath
|
||||
|
||||
- entry.path is already absolute (from smartfs); avoid joining it with dirPath which produced incorrect paths
|
||||
- Fixes file copy path construction so plugins.fs.file(fullPath).copy(destPath) uses the correct source path
|
||||
|
||||
## 2026-02-24 - 2.9.0 - feat(exports)
|
||||
expose mod_custom, mod_output and interfaces from entry; make processSingleBundle public
|
||||
|
||||
- Exported ./mod_custom, ./mod_output and ./interfaces from ts/index.ts to expose these modules in the public API.
|
||||
- Changed processSingleBundle in ts/mod_custom/index.ts from private to public to allow programmatic invocation.
|
||||
- Non-breaking API expansion; recommend a minor version bump.
|
||||
|
||||
## 2026-02-24 - 2.8.4 - fix()
|
||||
no changes — empty diff, nothing to commit
|
||||
|
||||
- Diff contained no modifications; no release required
|
||||
|
||||
## 2026-01-23 - 2.8.3 - fix(mod_output)
|
||||
use pattern base dir when computing relative paths for files to serve
|
||||
|
||||
- Compute relativePath using the pattern base directory (dirPath) instead of this.cwd to ensure correct web-serving paths for absolute or relative entry.path values.
|
||||
- File changed: ts/mod_output/index.ts — replaces plugins.path.relative(this.cwd, fullPath) with plugins.path.relative(dirPath, fullPath) and adds clarifying comment.
|
||||
|
||||
## 2026-01-23 - 2.8.2 - fix(mod_output)
|
||||
resolve absolute and relative entry.path correctly when adding files
|
||||
|
||||
- Add check for plugins.path.isAbsolute(entry.path) to avoid incorrectly joining absolute paths with dirPath
|
||||
- Use entry.path directly when it's absolute, otherwise join with dirPath
|
||||
- Ensures correct relativePath calculation and prevents invalid file reads
|
||||
|
||||
## 2026-01-12 - 2.8.1 - fix(readme)
|
||||
document maxLineLength option for base64ts output and add example and tip
|
||||
|
||||
- Add documented `maxLineLength` configuration option (number, default 0 = unlimited) for `base64ts` output.
|
||||
- Include example config showing `maxLineLength: 200`.
|
||||
- Add a tip recommending setting `maxLineLength` to split long base64 strings when using AI tools with line-length limits.
|
||||
|
||||
## 2026-01-12 - 2.8.0 - feat(tsbundle)
|
||||
add configurable maxLineLength for base64ts output and improve build/error handling in child builds
|
||||
|
||||
- Add optional maxLineLength?: number to IBundleConfig to control max characters per line for base64ts output (0 or undefined = unlimited).
|
||||
- Support splitting base64 strings when maxLineLength is specified; generateTypeScript(maxLineLength?) and writeToFile(outputPath, maxLineLength?) updated to accept and apply this setting.
|
||||
- Pass bundleConfig.maxLineLength through in mod_custom so base64ts output respects bundle configuration.
|
||||
- Wrap TsBundle.build in mod_custom with try/catch to log failures and skip output handling when build fails.
|
||||
- tsbundle.class now rejects the bundle promise when the child process exits with a non-zero status.
|
||||
- mod_esbuild child process now awaits builds, exits with appropriate success/failure codes, and formats esbuild errors for clearer console output.
|
||||
|
||||
## 2026-01-12 - 2.7.4 - fix(deps)
|
||||
bump @push.rocks/smartcli dependency to ^4.0.20
|
||||
|
||||
- @push.rocks/smartcli: ^4.0.19 → ^4.0.20
|
||||
- Patch-level dependency update with no breaking changes
|
||||
|
||||
## 2026-01-12 - 2.7.3 - fix(mod_output)
|
||||
wrap long base64 file contents and format generated TypeScript output to avoid extremely long lines
|
||||
|
||||
|
||||
30
package.json
30
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@git.zone/tsbundle",
|
||||
"version": "2.7.3",
|
||||
"version": "2.10.0",
|
||||
"private": false,
|
||||
"description": "a multi-bundler tool supporting esbuild, rolldown, and rspack for painless bundling of web projects",
|
||||
"main": "dist_ts/index.js",
|
||||
@@ -17,29 +17,29 @@
|
||||
"tsbundle": "cli.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^3.1.2",
|
||||
"@git.zone/tsrun": "^2.0.0",
|
||||
"@git.zone/tstest": "^3.1.3",
|
||||
"@types/node": "^24.10.1"
|
||||
"@git.zone/tsbuild": "^4.3.0",
|
||||
"@git.zone/tsrun": "^2.0.1",
|
||||
"@git.zone/tstest": "^3.5.1",
|
||||
"@types/node": "^25.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@push.rocks/early": "^4.0.4",
|
||||
"@push.rocks/npmextra": "^5.1.3",
|
||||
"@push.rocks/smartcli": "^4.0.19",
|
||||
"@push.rocks/smartinteract": "^2.0.16",
|
||||
"@push.rocks/smartconfig": "^6.0.1",
|
||||
"@push.rocks/smartcli": "^4.0.20",
|
||||
"@push.rocks/smartdelay": "^3.0.5",
|
||||
"@push.rocks/smartfs": "^1.1.3",
|
||||
"@push.rocks/smartlog": "^3.1.8",
|
||||
"@push.rocks/smartfs": "^1.5.0",
|
||||
"@push.rocks/smartinteract": "^2.0.16",
|
||||
"@push.rocks/smartlog": "^3.2.1",
|
||||
"@push.rocks/smartlog-destination-local": "^9.0.2",
|
||||
"@push.rocks/smartpath": "^6.0.0",
|
||||
"@push.rocks/smartpromise": "^4.2.3",
|
||||
"@push.rocks/smartspawn": "^3.0.3",
|
||||
"@rspack/core": "^1.6.5",
|
||||
"@rspack/core": "^1.7.10",
|
||||
"@types/html-minifier": "^4.0.6",
|
||||
"esbuild": "^0.27.0",
|
||||
"esbuild": "^0.27.4",
|
||||
"html-minifier": "^4.0.0",
|
||||
"rolldown": "1.0.0-beta.52",
|
||||
"typescript": "5.9.3"
|
||||
"rolldown": "1.0.0-rc.11",
|
||||
"typescript": "6.0.2"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
@@ -50,7 +50,7 @@
|
||||
"dist_ts_web/**/*",
|
||||
"assets/**/*",
|
||||
"cli.js",
|
||||
"npmextra.json",
|
||||
".smartconfig.json",
|
||||
"readme.md"
|
||||
],
|
||||
"browserslist": [
|
||||
|
||||
3827
pnpm-lock.yaml
generated
3827
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,15 @@
|
||||
# tsbundle Hints and Findings
|
||||
|
||||
## Recent Updates (2025-11-23)
|
||||
## Recent Updates (2026-03-24)
|
||||
|
||||
- Migrated from @push.rocks/smartfile v11 to @push.rocks/smartfs v1.1.0
|
||||
- All filesystem operations now use async smartfs API
|
||||
- Removed @push.rocks/tapbundle (now imported from @git.zone/tstest/tapbundle)
|
||||
- All bundlers (esbuild, rolldown, rspack) updated to latest versions
|
||||
- Removed deprecated rolldown experimental.enableComposingJsPlugins option
|
||||
- Upgraded all dependencies to latest versions
|
||||
- Migrated from npmextra.json to smartconfig.json (renamed file, updated all references)
|
||||
- Renamed `npmextra` import to `smartconfig` in plugins.ts
|
||||
- Fixed rolldown deprecation: `inlineDynamicImports` replaced with `codeSplitting: false`
|
||||
- Major version bumps: tsbuild ^3.1.2 -> ^4.3.0, TypeScript 5.9.3 -> 6.0.2
|
||||
- Rolldown upgraded from 1.0.0-beta.52 to 1.0.0-rc.11
|
||||
- @rspack/core upgraded from ^1.6.5 to ^1.7.10
|
||||
- @types/node upgraded from ^24.10.1 to ^25.5.0
|
||||
|
||||
## Bundler Architecture
|
||||
|
||||
@@ -16,41 +19,16 @@
|
||||
|
||||
## Bundler Implementations
|
||||
|
||||
- **esbuild** (default): Fully implemented, production ready, 3.9K minified
|
||||
- **rolldown**: Implemented and working (beta v1.0.0-beta.51), produces smallest bundles (1.0K minified)
|
||||
- **rspack**: Implemented and working (v1.6.4), webpack-compatible API, 6.3K minified
|
||||
- **rollup**: Removed (was never implemented)
|
||||
- **parcel**: Removed (was never implemented)
|
||||
- **esbuild** (default): Fully implemented, production ready
|
||||
- **rolldown**: Implemented and working (1.0.0-rc.11), produces smallest bundles
|
||||
- **rspack**: Implemented and working (v1.7.10), webpack-compatible API
|
||||
|
||||
## Adding New Bundlers
|
||||
## Configuration
|
||||
|
||||
To add a new bundler, you need:
|
||||
|
||||
1. Update `ICliOptions` interface to include the bundler name
|
||||
2. Add case in `getBundlerPath()` switch statement
|
||||
3. Create `mod_<bundler>/` directory with:
|
||||
- `plugins.ts`: Import and re-export the bundler
|
||||
- `index.child.ts`: Implement TsBundleProcess class with buildTest() and buildProduction()
|
||||
4. Add bundler to package.json dependencies
|
||||
|
||||
## Rolldown Specific Notes
|
||||
|
||||
- Rolldown is in beta (v1.0.0-beta.18) but working well
|
||||
- API: Use `rolldown()` function directly, not `rolldown.rolldown()`
|
||||
- Output options go in the `write()` method, not the initial config
|
||||
- Uses `dir` and `entryFileNames` instead of `file` for output (handles dynamic imports)
|
||||
- Includes `inlineDynamicImports: true` to avoid chunk splitting issues
|
||||
- Produces smaller minified bundles than esbuild (1.5K vs 2.2K in tests)
|
||||
- Supports TypeScript via `resolve.tsconfigFilename`
|
||||
|
||||
## Rspack Specific Notes
|
||||
|
||||
- Rspack v1.3.15 - stable and production ready
|
||||
- Uses webpack-compatible API (callback-based)
|
||||
- Built-in SWC loader for TypeScript transpilation
|
||||
- Produces larger bundles than esbuild/rolldown due to webpack runtime overhead
|
||||
- Best choice if you need webpack compatibility or advanced features
|
||||
- Supports ES modules output via `experiments.outputModule: true`
|
||||
- Config file: `smartconfig.json` (previously `npmextra.json`)
|
||||
- Config key: `@git.zone/tsbundle` with `bundles` array
|
||||
- Supports `bundle` and `base64ts` output modes
|
||||
- Interactive init wizard via `tsbundle init`
|
||||
|
||||
## CLI Usage
|
||||
|
||||
@@ -61,5 +39,4 @@ To add a new bundler, you need:
|
||||
|
||||
## Known Issues
|
||||
|
||||
- esbuild recently had splitting and tree-shaking disabled due to issues
|
||||
- The README still mentions "a bundler using rollup" but uses esbuild by default
|
||||
- Rolldown emits a warning about `preserveValueImports` in tsconfig - this is informational only
|
||||
|
||||
124
readme.md
124
readme.md
@@ -1,6 +1,6 @@
|
||||
# @git.zone/tsbundle
|
||||
|
||||
A powerful multi-bundler tool supporting **esbuild**, **rolldown**, and **rspack** for painless bundling of web projects. 🚀
|
||||
A powerful multi-bundler tool supporting **esbuild**, **rolldown**, and **rspack** for painless bundling of web projects.
|
||||
|
||||
## Issue Reporting and Security
|
||||
|
||||
@@ -10,16 +10,13 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
||||
|
||||
```bash
|
||||
# Global installation for CLI usage
|
||||
npm install -g @git.zone/tsbundle
|
||||
pnpm add -g @git.zone/tsbundle
|
||||
|
||||
# Local installation for project usage
|
||||
npm install --save-dev @git.zone/tsbundle
|
||||
|
||||
# Or with pnpm
|
||||
pnpm add -g @git.zone/tsbundle
|
||||
pnpm add --save-dev @git.zone/tsbundle
|
||||
```
|
||||
|
||||
## Quick Start ⚡
|
||||
## Quick Start
|
||||
|
||||
### Interactive Setup
|
||||
|
||||
@@ -29,11 +26,11 @@ The easiest way to get started is with the interactive wizard:
|
||||
tsbundle init
|
||||
```
|
||||
|
||||
This will guide you through setting up your bundle configuration with preset options:
|
||||
This guides you through setting up your bundle configuration with preset options:
|
||||
|
||||
- **element** - Web component / element bundle (`./ts_web/index.ts` → `./dist_bundle/bundle.js`)
|
||||
- **website** - Full website with HTML and assets (`./ts_web/index.ts` → `./dist_serve/bundle.js`)
|
||||
- **npm** - NPM package bundle (`./ts/index.ts` → `./dist_bundle/bundle.js`)
|
||||
- **element** - Web component / element bundle (`./ts_web/index.ts` -> `./dist_bundle/bundle.js`)
|
||||
- **website** - Full website with HTML and assets (`./ts_web/index.ts` -> `./dist_serve/bundle.js`)
|
||||
- **npm** - NPM package bundle (`./ts/index.ts` -> `./dist_bundle/bundle.js`)
|
||||
- **custom** - Configure everything manually
|
||||
|
||||
### Build Your Bundles
|
||||
@@ -44,19 +41,19 @@ Once configured, simply run:
|
||||
tsbundle
|
||||
```
|
||||
|
||||
That's it! Your bundles will be built according to your `npmextra.json` configuration.
|
||||
Your bundles will be built according to your `smartconfig.json` configuration.
|
||||
|
||||
## CLI Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `tsbundle` | Build all bundles from `npmextra.json` configuration |
|
||||
| `tsbundle` | Build all bundles from `smartconfig.json` configuration |
|
||||
| `tsbundle custom` | Same as above (explicit) |
|
||||
| `tsbundle init` | Interactive wizard to create/update bundle configuration |
|
||||
|
||||
## Configuration 📝
|
||||
## Configuration
|
||||
|
||||
tsbundle uses `npmextra.json` for configuration. Here's an example:
|
||||
tsbundle uses `smartconfig.json` for configuration. Here's an example:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -91,6 +88,7 @@ tsbundle uses `npmextra.json` for configuration. Here's an example:
|
||||
| `bundler` | `"esbuild"` \| `"rolldown"` \| `"rspack"` | `"esbuild"` | Which bundler to use |
|
||||
| `production` | `boolean` | `false` | Enable minification |
|
||||
| `includeFiles` | `string[]` | `[]` | Glob patterns for additional files (HTML, assets) |
|
||||
| `maxLineLength` | `number` | `0` (unlimited) | For `base64ts` mode: max chars per line in output |
|
||||
|
||||
### Output Modes
|
||||
|
||||
@@ -108,20 +106,24 @@ export const files: { path: string; contentBase64: string }[] = [
|
||||
];
|
||||
```
|
||||
|
||||
## Available Bundlers 🔧
|
||||
If you're working with AI tools that have line length limitations, set `maxLineLength` (e.g., `200`) to split long base64 strings across multiple lines.
|
||||
|
||||
tsbundle supports three modern bundlers:
|
||||
## Available Bundlers
|
||||
|
||||
| Bundler | Speed | Best For |
|
||||
|---------|-------|----------|
|
||||
| **esbuild** | ⚡⚡⚡ Blazing fast | Development, quick iterations |
|
||||
| **rolldown** | ⚡⚡ Fast | Production builds, tree-shaking |
|
||||
| **rspack** | ⚡⚡ Fast | Webpack compatibility |
|
||||
tsbundle supports three modern bundlers, each with different strengths:
|
||||
|
||||
| Bundler | Speed | Bundle Size | Best For |
|
||||
|---------|-------|-------------|----------|
|
||||
| **esbuild** | Fastest | Medium | Development, quick iterations |
|
||||
| **rolldown** | Fast | Smallest | Production builds, tree-shaking |
|
||||
| **rspack** | Fast | Largest (webpack runtime) | Webpack compatibility |
|
||||
|
||||
## API Usage
|
||||
|
||||
### TsBundle Class
|
||||
|
||||
The core bundling class, usable programmatically:
|
||||
|
||||
```typescript
|
||||
import { TsBundle } from '@git.zone/tsbundle';
|
||||
|
||||
@@ -132,15 +134,17 @@ await bundler.build(
|
||||
'./src/index.ts', // Entry point
|
||||
'./dist/bundle.js', // Output path
|
||||
{
|
||||
bundler: 'esbuild',
|
||||
bundler: 'esbuild', // 'esbuild' | 'rolldown' | 'rspack'
|
||||
production: true
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Each bundler runs in a separate child process via `smartspawn.ThreadSimple`, keeping the main process clean and isolated from bundler-specific dependencies.
|
||||
|
||||
### HtmlHandler Class
|
||||
|
||||
Process and minify HTML files:
|
||||
Process and optionally minify HTML files:
|
||||
|
||||
```typescript
|
||||
import { HtmlHandler } from '@git.zone/tsbundle';
|
||||
@@ -156,7 +160,7 @@ await htmlHandler.processHtml({
|
||||
|
||||
### AssetsHandler Class
|
||||
|
||||
Handle static assets:
|
||||
Copy static assets between directories:
|
||||
|
||||
```typescript
|
||||
import { AssetsHandler } from '@git.zone/tsbundle';
|
||||
@@ -169,59 +173,34 @@ await assetsHandler.processAssets({
|
||||
});
|
||||
```
|
||||
|
||||
## Complete Example 🎯
|
||||
### Base64TsOutput Class
|
||||
|
||||
### 1. Initialize your project
|
||||
Generate TypeScript files with base64-encoded content for embedding:
|
||||
|
||||
```bash
|
||||
tsbundle init
|
||||
```typescript
|
||||
import { Base64TsOutput } from '@git.zone/tsbundle';
|
||||
|
||||
const output = new Base64TsOutput(process.cwd());
|
||||
output.addFile('bundle.js', bundleBuffer);
|
||||
await output.addFilesFromGlob('./html/**/*.html');
|
||||
await output.writeToFile('./ts/embedded-bundle.ts', 200); // optional maxLineLength
|
||||
```
|
||||
|
||||
Select "website" preset for a full web application setup.
|
||||
### CustomBundleHandler Class
|
||||
|
||||
### 2. Your generated config
|
||||
Process multiple bundle configurations from `smartconfig.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"@git.zone/tsbundle": {
|
||||
"bundles": [
|
||||
{
|
||||
"from": "./ts_web/index.ts",
|
||||
"to": "./dist_serve/bundle.js",
|
||||
"outputMode": "bundle",
|
||||
"bundler": "esbuild",
|
||||
"includeFiles": ["./html/**/*.html", "./assets/**/*"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```typescript
|
||||
import { CustomBundleHandler } from '@git.zone/tsbundle';
|
||||
|
||||
const handler = new CustomBundleHandler(process.cwd());
|
||||
const hasConfig = await handler.loadConfig();
|
||||
if (hasConfig) {
|
||||
await handler.processAllBundles();
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Create your entry point
|
||||
|
||||
```typescript
|
||||
// ts_web/index.ts
|
||||
console.log('Hello from tsbundle! 🚀');
|
||||
|
||||
export const app = {
|
||||
version: '1.0.0',
|
||||
init() {
|
||||
console.log('App initialized');
|
||||
}
|
||||
};
|
||||
|
||||
app.init();
|
||||
```
|
||||
|
||||
### 4. Build
|
||||
|
||||
```bash
|
||||
tsbundle
|
||||
```
|
||||
|
||||
Your bundle is ready in `./dist_serve/bundle.js` along with any HTML and assets!
|
||||
|
||||
## Embedding for Deno Compile 🦕
|
||||
## Embedding for Deno Compile
|
||||
|
||||
For single-executable scenarios with Deno:
|
||||
|
||||
@@ -241,7 +220,8 @@ Config:
|
||||
"outputMode": "base64ts",
|
||||
"bundler": "esbuild",
|
||||
"production": true,
|
||||
"includeFiles": ["./html/index.html"]
|
||||
"includeFiles": ["./html/index.html"],
|
||||
"maxLineLength": 200
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -260,7 +240,7 @@ const bundleContent = atob(bundle.contentBase64);
|
||||
const htmlContent = atob(html.contentBase64);
|
||||
```
|
||||
|
||||
## Project Structure Recommendations 📁
|
||||
## Project Structure Recommendations
|
||||
|
||||
```
|
||||
your-project/
|
||||
@@ -273,7 +253,7 @@ your-project/
|
||||
├── assets/ # Static assets (images, fonts, etc.)
|
||||
├── dist_bundle/ # Output for element/npm bundles
|
||||
├── dist_serve/ # Output for website bundles
|
||||
└── npmextra.json # tsbundle configuration
|
||||
└── smartconfig.json # tsbundle configuration
|
||||
```
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@git.zone/tsbundle',
|
||||
version: '2.7.3',
|
||||
version: '2.10.0',
|
||||
description: 'a multi-bundler tool supporting esbuild, rolldown, and rspack for painless bundling of web projects'
|
||||
}
|
||||
|
||||
@@ -11,4 +11,7 @@ early.stop();
|
||||
export * from './tsbundle.class.tsbundle.js';
|
||||
export * from './mod_assets/index.js';
|
||||
export * from './mod_html/index.js';
|
||||
export * from './mod_custom/index.js';
|
||||
export * from './mod_output/index.js';
|
||||
export * from './interfaces/index.js';
|
||||
export { runCli };
|
||||
|
||||
@@ -17,13 +17,16 @@ export interface IEnvTransportOptions {
|
||||
export type TOutputMode = 'bundle' | 'base64ts';
|
||||
export type TBundler = 'esbuild' | 'rolldown' | 'rspack';
|
||||
|
||||
export type TIncludeFile = string | { from: string; to: string };
|
||||
|
||||
export interface IBundleConfig {
|
||||
from: string;
|
||||
to: string;
|
||||
outputMode?: TOutputMode;
|
||||
bundler?: TBundler;
|
||||
production?: boolean;
|
||||
includeFiles?: string[];
|
||||
includeFiles?: TIncludeFile[];
|
||||
maxLineLength?: number; // For base64ts output: max chars per line. 0 or undefined = unlimited (default)
|
||||
}
|
||||
|
||||
export interface ITsbundleConfig {
|
||||
|
||||
@@ -16,11 +16,11 @@ export class CustomBundleHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration from npmextra.json
|
||||
* Load configuration from smartconfig.json
|
||||
*/
|
||||
public async loadConfig(): Promise<boolean> {
|
||||
const npmextraInstance = new plugins.npmextra.Npmextra(this.cwd);
|
||||
this.config = npmextraInstance.dataFor<interfaces.ITsbundleConfig>('@git.zone/tsbundle', {
|
||||
const smartconfigInstance = new plugins.smartconfig.Smartconfig(this.cwd);
|
||||
this.config = smartconfigInstance.dataFor<interfaces.ITsbundleConfig>('@git.zone/tsbundle', {
|
||||
bundles: [],
|
||||
});
|
||||
|
||||
@@ -48,7 +48,7 @@ export class CustomBundleHandler {
|
||||
/**
|
||||
* Process a single bundle configuration
|
||||
*/
|
||||
private async processSingleBundle(bundleConfig: interfaces.IBundleConfig): Promise<void> {
|
||||
public async processSingleBundle(bundleConfig: interfaces.IBundleConfig): Promise<void> {
|
||||
const outputMode = bundleConfig.outputMode || 'bundle';
|
||||
const bundler = bundleConfig.bundler || 'esbuild';
|
||||
|
||||
@@ -61,6 +61,7 @@ export class CustomBundleHandler {
|
||||
|
||||
// Build the bundle to temp location
|
||||
const tsbundle = new TsBundle();
|
||||
try {
|
||||
await tsbundle.build(
|
||||
this.cwd,
|
||||
bundleConfig.from,
|
||||
@@ -70,6 +71,11 @@ export class CustomBundleHandler {
|
||||
production: bundleConfig.production || false,
|
||||
}
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error(`\n\x1b[31m❌ Bundle failed: ${bundleConfig.from} -> ${bundleConfig.to}\x1b[0m`);
|
||||
// Don't re-print error details - they were already shown by the child process
|
||||
return; // Skip output handling since build failed
|
||||
}
|
||||
|
||||
if (outputMode === 'base64ts') {
|
||||
await this.handleBase64TsOutput(bundleConfig, tempBundlePath);
|
||||
@@ -99,13 +105,17 @@ export class CustomBundleHandler {
|
||||
|
||||
// Add included files
|
||||
if (bundleConfig.includeFiles && bundleConfig.includeFiles.length > 0) {
|
||||
for (const pattern of bundleConfig.includeFiles) {
|
||||
await base64Output.addFilesFromGlob(pattern);
|
||||
for (const entry of bundleConfig.includeFiles) {
|
||||
if (typeof entry === 'string') {
|
||||
await base64Output.addFilesFromGlob(entry);
|
||||
} else {
|
||||
await base64Output.addFileWithServePath(entry.from, entry.to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write the TypeScript output
|
||||
await base64Output.writeToFile(bundleConfig.to);
|
||||
await base64Output.writeToFile(bundleConfig.to, bundleConfig.maxLineLength);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,7 +139,8 @@ export class CustomBundleHandler {
|
||||
const htmlHandler = new HtmlHandler();
|
||||
const outputDir = plugins.path.dirname(toPath);
|
||||
|
||||
for (const pattern of bundleConfig.includeFiles) {
|
||||
for (const entry of bundleConfig.includeFiles) {
|
||||
const pattern = typeof entry === 'string' ? entry : entry.from;
|
||||
await this.copyIncludedFiles(pattern, outputDir);
|
||||
}
|
||||
}
|
||||
@@ -165,7 +176,8 @@ export class CustomBundleHandler {
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory && regex.test(entry.name)) {
|
||||
const fullPath = plugins.path.join(dirPath, entry.path);
|
||||
// entry.path is already absolute from smartfs
|
||||
const fullPath = entry.path;
|
||||
const relativePath = plugins.path.relative(this.cwd, fullPath);
|
||||
const destPath = plugins.path.join(outputDir, plugins.path.basename(entry.path));
|
||||
await plugins.fs.directory(plugins.path.dirname(destPath)).create();
|
||||
|
||||
@@ -43,6 +43,7 @@ export class TsBundleProcess {
|
||||
sourcemap: true,
|
||||
format: 'esm',
|
||||
target: 'es2022',
|
||||
keepNames: true,
|
||||
entryNames: plugins.path.parse(toArg).name,
|
||||
outdir: plugins.path.parse(toArg).dir,
|
||||
splitting: false,
|
||||
@@ -67,6 +68,7 @@ export class TsBundleProcess {
|
||||
format: 'esm',
|
||||
target: 'es2022',
|
||||
minify: true,
|
||||
keepNames: true,
|
||||
entryNames: plugins.path.parse(toArg).name,
|
||||
outdir: plugins.path.parse(toArg).dir,
|
||||
tsconfig: paths.tsconfigPath,
|
||||
@@ -79,6 +81,7 @@ export class TsBundleProcess {
|
||||
}
|
||||
|
||||
const run = async () => {
|
||||
try {
|
||||
console.log('running spawned compilation process');
|
||||
const transportOptions: interfaces.IEnvTransportOptions = JSON.parse(
|
||||
process.env.transportOptions,
|
||||
@@ -90,7 +93,7 @@ const run = async () => {
|
||||
const tsbundleProcessInstance = new TsBundleProcess();
|
||||
if (transportOptions.mode === 'test') {
|
||||
console.log('building for test:');
|
||||
tsbundleProcessInstance.buildTest(
|
||||
await tsbundleProcessInstance.buildTest(
|
||||
plugins.smartpath.transform.makeAbsolute(
|
||||
transportOptions.from,
|
||||
process.cwd(),
|
||||
@@ -103,7 +106,7 @@ const run = async () => {
|
||||
);
|
||||
} else {
|
||||
console.log('building for production:');
|
||||
tsbundleProcessInstance.buildProduction(
|
||||
await tsbundleProcessInstance.buildProduction(
|
||||
plugins.smartpath.transform.makeAbsolute(
|
||||
transportOptions.from,
|
||||
process.cwd(),
|
||||
@@ -115,6 +118,29 @@ const run = async () => {
|
||||
transportOptions.argv,
|
||||
);
|
||||
}
|
||||
process.exit(0);
|
||||
} catch (error: any) {
|
||||
console.error('\n\x1b[31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m');
|
||||
console.error('\x1b[31m❌ BUILD FAILED\x1b[0m');
|
||||
console.error('\x1b[31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m\n');
|
||||
|
||||
if (error.errors && Array.isArray(error.errors)) {
|
||||
// esbuild errors - format them nicely
|
||||
console.error(`Found ${error.errors.length} error(s):\n`);
|
||||
for (const err of error.errors) {
|
||||
const file = err.location?.file || 'unknown';
|
||||
const line = err.location?.line || '?';
|
||||
const column = err.location?.column || '?';
|
||||
console.error(` \x1b[36m${file}\x1b[0m:\x1b[33m${line}\x1b[0m:\x1b[33m${column}\x1b[0m`);
|
||||
console.error(` ${err.text}\n`);
|
||||
}
|
||||
} else {
|
||||
console.error(error.message || error);
|
||||
}
|
||||
|
||||
console.error('\x1b[31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m\n');
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
run();
|
||||
|
||||
@@ -36,20 +36,20 @@ const PRESETS: Record<string, { description: string; config: interfaces.IBundleC
|
||||
|
||||
export class InitHandler {
|
||||
private cwd: string;
|
||||
private npmextraPath: string;
|
||||
private smartconfigPath: string;
|
||||
|
||||
constructor(cwd: string = paths.cwd) {
|
||||
this.cwd = cwd;
|
||||
this.npmextraPath = plugins.path.join(this.cwd, 'npmextra.json');
|
||||
this.smartconfigPath = plugins.path.join(this.cwd, 'smartconfig.json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load existing npmextra.json or create empty config
|
||||
* Load existing smartconfig.json or create empty config
|
||||
*/
|
||||
private async loadExistingConfig(): Promise<any> {
|
||||
const fileExists = await plugins.fs.file(this.npmextraPath).exists();
|
||||
const fileExists = await plugins.fs.file(this.smartconfigPath).exists();
|
||||
if (fileExists) {
|
||||
const content = (await plugins.fs.file(this.npmextraPath).encoding('utf8').read()) as string;
|
||||
const content = (await plugins.fs.file(this.smartconfigPath).encoding('utf8').read()) as string;
|
||||
try {
|
||||
return JSON.parse(content);
|
||||
} catch {
|
||||
@@ -60,12 +60,12 @@ export class InitHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Save config to npmextra.json
|
||||
* Save config to smartconfig.json
|
||||
*/
|
||||
private async saveConfig(config: any): Promise<void> {
|
||||
const content = JSON.stringify(config, null, 2);
|
||||
await plugins.fs.file(this.npmextraPath).encoding('utf8').write(content);
|
||||
console.log(`\n✅ Configuration saved to npmextra.json`);
|
||||
await plugins.fs.file(this.smartconfigPath).encoding('utf8').write(content);
|
||||
console.log(`\n✅ Configuration saved to smartconfig.json`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,15 +73,15 @@ export class InitHandler {
|
||||
*/
|
||||
public async runWizard(): Promise<void> {
|
||||
console.log('\n🚀 tsbundle configuration wizard\n');
|
||||
console.log('This wizard will help you configure bundle settings in npmextra.json.\n');
|
||||
console.log('This wizard will help you configure bundle settings in smartconfig.json.\n');
|
||||
|
||||
const npmextraJson = await this.loadExistingConfig();
|
||||
const smartconfigJson = await this.loadExistingConfig();
|
||||
|
||||
if (!npmextraJson['@git.zone/tsbundle']) {
|
||||
npmextraJson['@git.zone/tsbundle'] = { bundles: [] };
|
||||
if (!smartconfigJson['@git.zone/tsbundle']) {
|
||||
smartconfigJson['@git.zone/tsbundle'] = { bundles: [] };
|
||||
}
|
||||
|
||||
const existingBundles = npmextraJson['@git.zone/tsbundle'].bundles || [];
|
||||
const existingBundles = smartconfigJson['@git.zone/tsbundle'].bundles || [];
|
||||
|
||||
if (existingBundles.length > 0) {
|
||||
console.log(`Found ${existingBundles.length} existing bundle configuration(s):\n`);
|
||||
@@ -95,7 +95,7 @@ export class InitHandler {
|
||||
while (addMore) {
|
||||
const bundle = await this.configureSingleBundle();
|
||||
if (bundle) {
|
||||
npmextraJson['@git.zone/tsbundle'].bundles.push(bundle);
|
||||
smartconfigJson['@git.zone/tsbundle'].bundles.push(bundle);
|
||||
console.log(`\n✅ Bundle configuration added!`);
|
||||
}
|
||||
|
||||
@@ -112,10 +112,10 @@ export class InitHandler {
|
||||
addMore = answers.getAnswerFor('addAnother');
|
||||
}
|
||||
|
||||
await this.saveConfig(npmextraJson);
|
||||
await this.saveConfig(smartconfigJson);
|
||||
|
||||
console.log('\n📋 Final configuration:\n');
|
||||
const bundles = npmextraJson['@git.zone/tsbundle'].bundles;
|
||||
const bundles = smartconfigJson['@git.zone/tsbundle'].bundles;
|
||||
bundles.forEach((bundle: interfaces.IBundleConfig, i: number) => {
|
||||
console.log(` Bundle ${i + 1}:`);
|
||||
console.log(` From: ${bundle.from}`);
|
||||
@@ -123,7 +123,8 @@ export class InitHandler {
|
||||
console.log(` Mode: ${bundle.outputMode || 'bundle'}`);
|
||||
console.log(` Bundler: ${bundle.bundler || 'esbuild'}`);
|
||||
if (bundle.includeFiles && bundle.includeFiles.length > 0) {
|
||||
console.log(` Include: ${bundle.includeFiles.join(', ')}`);
|
||||
const display = bundle.includeFiles.map(f => typeof f === 'string' ? f : `${f.from} -> ${f.to}`);
|
||||
console.log(` Include: ${display.join(', ')}`);
|
||||
}
|
||||
console.log('');
|
||||
});
|
||||
@@ -168,7 +169,8 @@ export class InitHandler {
|
||||
console.log(` Mode: ${preset.config.outputMode}`);
|
||||
console.log(` Bundler: ${preset.config.bundler}`);
|
||||
if (preset.config.includeFiles && preset.config.includeFiles.length > 0) {
|
||||
console.log(` Include: ${preset.config.includeFiles.join(', ')}`);
|
||||
const display = preset.config.includeFiles.map(f => typeof f === 'string' ? f : `${f.from} -> ${f.to}`);
|
||||
console.log(` Include: ${display.join(', ')}`);
|
||||
}
|
||||
|
||||
const confirmInteract = new plugins.smartinteract.SmartInteract();
|
||||
@@ -293,14 +295,14 @@ export class InitHandler {
|
||||
/**
|
||||
* Configure files to include
|
||||
*/
|
||||
private async configureIncludeFiles(prefill?: string[]): Promise<string[]> {
|
||||
const includeFiles: string[] = [];
|
||||
private async configureIncludeFiles(prefill?: interfaces.TIncludeFile[]): Promise<interfaces.TIncludeFile[]> {
|
||||
const includeFiles: interfaces.TIncludeFile[] = [];
|
||||
let addMore = true;
|
||||
|
||||
// If we have prefilled values, show them first
|
||||
if (prefill && prefill.length > 0) {
|
||||
console.log('\nPre-configured include patterns:');
|
||||
prefill.forEach((p) => console.log(` - ${p}`));
|
||||
prefill.forEach((p) => console.log(` - ${typeof p === 'string' ? p : `${p.from} -> ${p.to}`}`));
|
||||
|
||||
const keepInteract = new plugins.smartinteract.SmartInteract();
|
||||
keepInteract.addQuestions([
|
||||
|
||||
@@ -22,6 +22,20 @@ export class Base64TsOutput {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a file with a custom serve path
|
||||
*/
|
||||
public async addFileWithServePath(fromPath: string, servePath: string): Promise<void> {
|
||||
const absolutePath = plugins.smartpath.transform.toAbsolute(fromPath, this.cwd) as string;
|
||||
const fileExists = await plugins.fs.file(absolutePath).exists();
|
||||
if (!fileExists) {
|
||||
console.log(`File does not exist: ${absolutePath}`);
|
||||
return;
|
||||
}
|
||||
const content = await plugins.fs.file(absolutePath).read();
|
||||
this.addFile(servePath, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add files matching a glob pattern
|
||||
*/
|
||||
@@ -56,8 +70,12 @@ export class Base64TsOutput {
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory && regex.test(entry.name)) {
|
||||
const fullPath = plugins.path.join(dirPath, entry.path);
|
||||
const relativePath = plugins.path.relative(this.cwd, fullPath);
|
||||
// entry.path may be absolute or relative - handle both cases
|
||||
const fullPath = plugins.path.isAbsolute(entry.path)
|
||||
? entry.path
|
||||
: plugins.path.join(dirPath, entry.path);
|
||||
// Use path relative to pattern's base dir (not cwd) for web serving
|
||||
const relativePath = plugins.path.relative(dirPath, fullPath);
|
||||
const content = await plugins.fs.file(fullPath).read();
|
||||
this.addFile(relativePath, content);
|
||||
}
|
||||
@@ -77,18 +95,26 @@ export class Base64TsOutput {
|
||||
|
||||
/**
|
||||
* Generate TypeScript file content
|
||||
* @param maxLineLength - Max chars per line for base64 strings. 0 or undefined = unlimited (default)
|
||||
*/
|
||||
public generateTypeScript(): string {
|
||||
const MAX_LINE_LENGTH = 200;
|
||||
public generateTypeScript(maxLineLength?: number): string {
|
||||
// Default behavior: no line splitting (unlimited)
|
||||
if (!maxLineLength || maxLineLength <= 0) {
|
||||
const filesJson = JSON.stringify(this.files, null, 2);
|
||||
return `// Auto-generated by tsbundle - do not edit
|
||||
export const files: { path: string; contentBase64: string }[] = ${filesJson};
|
||||
`;
|
||||
}
|
||||
|
||||
// Split base64 strings into chunks when maxLineLength is specified
|
||||
const formatBase64 = (base64: string): string => {
|
||||
if (base64.length <= MAX_LINE_LENGTH) {
|
||||
if (base64.length <= maxLineLength) {
|
||||
return `"${base64}"`;
|
||||
}
|
||||
|
||||
const chunks: string[] = [];
|
||||
for (let i = 0; i < base64.length; i += MAX_LINE_LENGTH) {
|
||||
chunks.push(base64.slice(i, i + MAX_LINE_LENGTH));
|
||||
for (let i = 0; i < base64.length; i += maxLineLength) {
|
||||
chunks.push(base64.slice(i, i + maxLineLength));
|
||||
}
|
||||
|
||||
return `"" +\n "${chunks.join('" +\n "')}"`;
|
||||
@@ -110,12 +136,14 @@ ${filesFormatted}
|
||||
|
||||
/**
|
||||
* Write the TypeScript file to disk
|
||||
* @param outputPath - Output file path
|
||||
* @param maxLineLength - Max chars per line for base64 strings. 0 or undefined = unlimited (default)
|
||||
*/
|
||||
public async writeToFile(outputPath: string): Promise<void> {
|
||||
public async writeToFile(outputPath: string, maxLineLength?: number): Promise<void> {
|
||||
const absolutePath = plugins.smartpath.transform.toAbsolute(outputPath, this.cwd) as string;
|
||||
const outputDir = plugins.path.dirname(absolutePath);
|
||||
await plugins.fs.directory(outputDir).create();
|
||||
const content = this.generateTypeScript();
|
||||
const content = this.generateTypeScript(maxLineLength);
|
||||
await plugins.fs.file(absolutePath).encoding('utf8').write(content);
|
||||
console.log(`Generated base64ts output: ${outputPath}`);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export class TsBundleProcess {
|
||||
entryFileNames: outputFilename,
|
||||
format: 'es',
|
||||
sourcemap: true,
|
||||
inlineDynamicImports: true,
|
||||
codeSplitting: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ export class TsBundleProcess {
|
||||
format: 'es',
|
||||
sourcemap: true,
|
||||
minify: true,
|
||||
inlineDynamicImports: true,
|
||||
codeSplitting: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as path from 'path';
|
||||
export { path };
|
||||
|
||||
// pushrocks scope
|
||||
import * as npmextra from '@push.rocks/npmextra';
|
||||
import * as smartconfig from '@push.rocks/smartconfig';
|
||||
import * as smartcli from '@push.rocks/smartcli';
|
||||
import * as smartfs from '@push.rocks/smartfs';
|
||||
import * as smartinteract from '@push.rocks/smartinteract';
|
||||
@@ -15,7 +15,7 @@ import * as smartpromise from '@push.rocks/smartpromise';
|
||||
import * as smartspawn from '@push.rocks/smartspawn';
|
||||
|
||||
export {
|
||||
npmextra,
|
||||
smartconfig,
|
||||
smartcli,
|
||||
smartfs,
|
||||
smartinteract,
|
||||
|
||||
@@ -45,7 +45,11 @@ export class TsBundle {
|
||||
);
|
||||
const childProcess = await threadsimple.start();
|
||||
childProcess.on('exit', (status) => {
|
||||
if (status !== 0) {
|
||||
done.reject(new Error(`Bundle build failed with exit code ${status}`));
|
||||
} else {
|
||||
done.resolve();
|
||||
}
|
||||
});
|
||||
await done.promise;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { runInit } from './mod_init/index.js';
|
||||
export const runCli = async () => {
|
||||
const tsBundleCli = new plugins.smartcli.Smartcli();
|
||||
|
||||
// Default command: run custom bundles from npmextra.json
|
||||
// Default command: run custom bundles from smartconfig.json
|
||||
tsBundleCli.standardCommand().subscribe(async (argvArg) => {
|
||||
await runCustomBundles();
|
||||
});
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {}
|
||||
"verbatimModuleSyntax": true
|
||||
},
|
||||
"exclude": ["dist_*/**/*.d.ts"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user