Skip to content

Instantly share code, notes, and snippets.

@0xBigBoss
Last active August 22, 2025 05:16
Show Gist options
  • Select an option

  • Save 0xBigBoss/488f8fb92608b7df85aab2130648e625 to your computer and use it in GitHub Desktop.

Select an option

Save 0xBigBoss/488f8fb92608b7df85aab2130648e625 to your computer and use it in GitHub Desktop.
zshy Asset Handling Guide - How to include custom files (LICENSE, README, JSON, proto, ABI) in TypeScript packages

zshy Asset Handling Guide

⚠️ IMPORTANT: zshy is NOT a bundler - it copies assets but does NOT transform them into JavaScript modules. Assets remain as separate files that must be accessed at runtime.

Overview

zshy automatically handles non-JavaScript assets and copies them to the output directory during builds. This feature enables including custom files like LICENSE, README.md, Protocol Buffer definitions, Ethereum ABIs, and other artifacts in your TypeScript packages without additional configuration.

Key Concept: zshy detects imported assets and copies them to the output directory, but does not make them available as JavaScript modules. The assets remain as separate files that you access at runtime.

How It Works

  1. Automatic Detection: Any imported file that isn't JavaScript/TypeScript (.js, .mjs, .cjs, .ts, .mts, .cts, .tsx) is treated as an asset
  2. Import Tracking: Assets are detected during TypeScript compilation when imported in source files
  3. Structure Preservation: Assets maintain their relative directory structure in the output
  4. Dual Build Support: Assets are copied for both ESM and CJS builds

Supported File Types

zshy supports any non-JS/TS file type, including:

  • LICENSE files (plain text)
  • README.md files (Markdown documentation)
  • JSON files (configuration, Ethereum ABIs, schemas)
  • Protocol Buffer definitions (.proto files)
  • CSS stylesheets (.css files)
  • Binary assets (images, fonts, etc.)
  • Configuration files (YAML, TOML, XML, etc.)
  • Any other custom file formats

Usage Pattern

WRONG: Importing Assets as Modules (Common Mistake)

// ❌ This DOES NOT WORK with zshy - it's not a bundler!
import licenseText from './assets/LICENSE';     // ERROR: Can't import as string
import readmeContent from './assets/README.md'; // ERROR: Can't import as string
import appConfig from './assets/config.json';   // ERROR: Can't import as object

// ❌ This fails because zshy doesn't transform assets into JS modules
export const config = {
  license: licenseText,  // undefined - import failed
  readme: readmeContent  // undefined - import failed
};

CORRECT: Import for Copying + Runtime Access

// ✅ Import to trigger copying (no content available at build time)
import './assets/styles.css';      // Copies to dist/assets/styles.css
import './assets/LICENSE';         // Copies to dist/assets/LICENSE
import './assets/README.md';       // Copies to dist/assets/README.md
import './contracts/schema.proto'; // Copies to dist/contracts/schema.proto

// ✅ Access copied files at runtime (browser)
export async function loadDocs() {
  const licenseResponse = await fetch('/assets/LICENSE');
  const licenseText = await licenseResponse.text();
  
  const readmeResponse = await fetch('/assets/README.md');
  const readmeContent = await readmeResponse.text();
  
  return { licenseText, readmeContent };
}

// ✅ Or access via file system (Node.js)
import { readFileSync } from 'fs';
import { join } from 'path';

export function loadDocsSync() {
  const licenseText = readFileSync(join(__dirname, 'assets/LICENSE'), 'utf-8');
  const readmeContent = readFileSync(join(__dirname, 'assets/README.md'), 'utf-8');
  return { licenseText, readmeContent };
}

ALTERNATIVE: Build-Time Content Embedding

If you need the content available as JavaScript variables (like the failed example), you need to embed it manually:

// ✅ Embed content as string literals (bypasses zshy's asset handling)
export const LICENSE = `MIT License

Copyright (c) 2024...`;

export const README = `# My Package

This is my package...`;

2. Directory Structure

src/
├── index.ts              # Main entry point
├── assets/
│   ├── LICENSE           # Plain text license
│   ├── README.md         # Documentation
│   ├── config.json       # Configuration
│   └── styles.css        # Stylesheets
└── contracts/
    ├── MyContract.abi.json  # Ethereum ABI
    └── schema.proto         # Protocol buffers

3. Build Output

dist/
├── index.js              # Compiled JavaScript (ESM)
├── index.cjs             # Compiled JavaScript (CJS)
├── index.d.ts            # Type definitions (ESM)
├── index.d.cts           # Type definitions (CJS)
├── assets/               # Copied assets (structure preserved)
│   ├── LICENSE
│   ├── README.md
│   ├── config.json
│   └── styles.css
└── contracts/
    ├── MyContract.abi.json
    └── schema.proto

Build Output Example

When running zshy, you'll see asset copying in the build output:

»  Found 5 asset import(s), copying to output directory...
»  Copied asset: ./src/assets/LICENSE → ./dist/assets/LICENSE
»  Copied asset: ./src/assets/README.md → ./dist/assets/README.md
»  Copied asset: ./src/assets/config.json → ./dist/assets/config.json
»  Copied asset: ./src/contracts/MyContract.abi.json → ./dist/contracts/MyContract.abi.json
»  Copied asset: ./src/contracts/schema.proto → ./dist/contracts/schema.proto

Key Features

  • Zero Configuration: Works automatically without setup
  • Import-Based: Only copies assets that are actually imported
  • Structure Preservation: Maintains relative directory paths
  • Dual Module Support: Works with both ESM and CJS builds
  • Universal File Support: Handles any non-JS/TS file type
  • Build Integration: Integrated into the standard zshy build process

Common Use Cases

WRONG: Treating zshy Like a Bundler

// ❌ These imports don't work - zshy is not webpack!
import tokenAbi from './contracts/Token.abi.json';
import userSchema from './schemas/user.proto';
import license from './LICENSE';
import defaultConfig from './config/default.json';

CORRECT: Asset Copying for Runtime Access

Web3/Blockchain Projects

// ✅ Import to copy files
import './contracts/Token.abi.json';
import './contracts/NFT.abi.json';

// ✅ Load at runtime
export async function loadTokenAbi() {
  const response = await fetch('/contracts/Token.abi.json');
  return response.json();
}

API/Microservices

// ✅ Import to copy files
import './schemas/user.proto';
import './schemas/config.proto';

// ✅ Access in Node.js at runtime
import { readFileSync } from 'fs';
export function loadUserSchema() {
  return readFileSync('./schemas/user.proto', 'utf-8');
}

Documentation & Legal

// ✅ Import to copy files to package
import './LICENSE';
import './CHANGELOG.md';
import './README.md';

// Files are included in npm package and can be served/accessed

Configuration & Assets

// ✅ CSS files get copied (common pattern)
import './styles/components.css';

// ✅ Config files get copied
import './config/default.json';

// ✅ Load config at runtime
export async function loadConfig() {
  const response = await fetch('/config/default.json');
  return response.json();
}

Technical Details

How zshy Asset Handling Actually Works

  • Detection Logic: Uses file extension checking to identify non-JS/TS files
  • Copy Operation: Files are physically copied from source to output directory
  • NO Transformation: Assets remain as-is - no bundling or module transformation
  • Import Purpose: import statements only trigger copying, they don't make content available
  • Copy Timing: Assets are copied during both CJS and ESM compilation phases
  • Path Resolution: Relative imports are resolved relative to the importing file
  • Deduplication: Same asset imported multiple times is only copied once
  • Error Handling: Missing assets are logged but don't fail the build

What zshy Does NOT Do

  • Does not transform assets into JavaScript modules
  • Does not make asset content available via imports
  • Does not bundle or inline assets
  • Does not provide build-time access to asset content
  • Does not work like webpack, Vite, or other bundlers

Mental Model

Think of zshy's asset handling like a smart cp command:

  • It tracks which files you import
  • It copies those files to the output directory
  • The files remain separate from your JavaScript code
  • You access them at runtime using normal file operations

Best Practices

Do's ✅

  1. Organize Assets: Use consistent directory structure (assets/, contracts/, etc.)
  2. Import for Copying: Use import './file.ext' to trigger copying
  3. Runtime Access: Load assets with fetch(), readFileSync(), or serve as static files
  4. Consider File Sizes: Large binary assets increase package size
  5. Version Control: Include all assets in your repository
  6. Documentation: Document that assets need runtime loading

Don'ts ❌

  1. Don't expect module imports: import content from './file.txt' won't work
  2. Don't add type declarations: Creating declare module "*.md" won't fix the fundamental issue
  3. Don't treat like webpack: zshy is not a bundler and doesn't transform assets
  4. Don't expect build-time content: Asset content is not available during compilation
  5. Don't mix patterns: Either use zshy's copying OR embed content as strings, not both

Verification

To verify asset handling is working:

  1. Import assets in your TypeScript files: import './assets/file.ext'
  2. Run zshy --verbose to see asset copying logs
  3. Check the dist/ directory for copied assets
  4. Confirm relative paths are preserved
  5. Test runtime access - verify you can load files with fetch() or fs.readFile()

Common Troubleshooting

"Cannot find module './file.md'" Error

Problem: TypeScript can't resolve the import
Solution: This is expected! The import is only for copying. Access the file at runtime instead.

"undefined" when trying to use imported content

Problem: Expecting import content from './file.md' to work
Solution: zshy doesn't transform assets. Use runtime loading or embed content as strings.

Build succeeds but can't access files

Problem: Files are copied but runtime access fails
Solution: Make sure your server serves the copied files, or use correct file paths for Node.js.

Summary

zshy's asset handling is perfect for including files in your package (documentation, configs, schemas), but not for importing content as modules. It's a smart file copier, not a bundler. Use it to include assets that you'll access at runtime, not at build time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment