Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/gunzip-scripts/.claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"permissions": {
"allow": [
"Bash(npm test)"
],
"deny": []
}
}
3 changes: 3 additions & 0 deletions packages/gunzip-scripts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
tests/generated
test-results
playwright-report
87 changes: 70 additions & 17 deletions packages/gunzip-scripts/README.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,102 @@
# gunzip-scripts

An easy way decompress on-chain, gzipped libraries in the browser.
An easy way to decompress on-chain, gzipped libraries in the browser with support for both UMD and ES modules.

## Available Versions

- **`gunzipScripts.js`** (4.5KB) - Default version for simple UMD/script injection
- **`gunzipScripts-esm.js`** (49.5KB) - Full ES module support with import rewriting

## How it works

This library looks for any `<script>` tags on the page with `type="text/javascript+gzip"` and inline source via `src="data:text/javascript;base64,…"`.
This library looks for `<script>` tags with special type attributes:

- `type="text/javascript+gzip"` - For UMD/regular scripts
- `type="text/javascript+gzip;module"` - For ES modules (ESM version only)

> The non-standard `type` attribute here prevents the browser from parsing/evaluating it immediately, allowing us to handle it ourselves.
Both use inline source via `src="data:application/gzip;base64,…"`.

For each script found, it decompresses the gzipped contents of the data URI and replaces the `<script>` element with a new one that includes the decompressed source code.
> The non-standard `type` attributes prevent the browser from parsing/evaluating immediately, allowing us to handle decompression ourselves.

> This is currently a destructive operation that does not preserve other `<script>` attributes. Please open an issue or PR if you need certain attributes preserved!
### Default Version
Decompresses gzipped contents and replaces `<script>` elements with new ones containing the decompressed source.

### ESM Version
Additionally supports ES modules with:
- Import path rewriting for relative imports
- Blob URL generation for modules
- Integration with es-module-shims for full ESM compatibility
- Support for `data-path` and `data-name` attributes for module identification

## How to use

When building your on-chain HTML string, it's best to include this library once between the gzipped libraries and where those libraries are used. For example:
### Default Version (UMD/Scripts)

For traditional libraries and UMD modules:

```html
<!-- gzipped libs -->
<script type="text/javascript+gzip" src="three.js.gz"></script>
<script type="text/javascript+gzip" src="data:application/gzip;base64,..."></script>

<!-- decompress gzipped libs -->
<script src="gunzipScripts.js"></script>

<!-- use decompressed libs -->
<script>
const scene = new THREE.Scene();
// ...
</script>
```

The `gunzipScripts.js` library will run immediately after inclusion on the page. If you have a situation where you need to run it again, but don't want to include the library again, you can call `gunzipScripts()`. This is a ~no-op if no elements are found that match the conditions mentioned above, so it's safe to call multiple times.
### ESM Version (ES Modules)

For ES modules with import/export:

```html
<!-- only need to include once -->
<script src="gunzipScripts.js"></script>
<!-- gzipped ES modules -->
<script type="text/javascript+gzip;module" data-path="./myCode.js" src="data:application/gzip;base64,..."></script>
<script type="text/javascript+gzip;module" data-path="./utils.js" src="data:application/gzip;base64,..."></script>

<!-- decompress and setup module system -->
<script src="gunzipScripts-esm.js"></script>

<!-- entry point - required for execution -->
<script type="module-shim">
import myCode from './myCode.js';
myCode.init();
</script>
```

**Important:** You need at least one regular (non-gzipped) `<script type="module-shim">` as an entry point to start execution. The gzipped modules are decompressed and made available for import, but they won't execute automatically.

<!-- more gzipped libs are added -->
<script type="text/javascript+gzip" src="three.js.gz"></script>
Both versions run automatically after inclusion. For manual control:

<!-- decompress before using -->
```html
<!-- Manual triggering -->
<script>
gunzipScripts();
// Wait for processing to complete (ESM version only)
document.addEventListener('gunzipScriptsReady', () => {
console.log('All modules processed and ready');
});

const scene = new THREE.Scene();
// Manual re-run (safe to call multiple times)
gunzipScripts();
</script>
```

## Module Attributes (ESM Version)

- `data-path="./path/to/module.js"` - Relative path for import resolution
- `data-name="moduleName"` - Named module for bare imports

Example:
```html
<script type="text/javascript+gzip;module"
data-path="./components/Button.js"
src="data:application/gzip;base64,..."></script>
```

Allows imports like:
```javascript
import { Button } from './components/Button.js';
```
3 changes: 3 additions & 0 deletions packages/gunzip-scripts/dist/gunzipScripts-esm.js

Large diffs are not rendered by default.

14 changes: 11 additions & 3 deletions packages/gunzip-scripts/package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
{
"name": "@ethfs/gunzip-scripts",
"private": true,
"version": "0.0.1",
"version": "0.0.2",
"scripts": {
"build": "esbuild src/gunzipScripts.ts --outfile=dist/gunzipScripts-$npm_package_version.js --bundle --minify"
"build": "node scripts/create-embedded-shims.js && esbuild src/gunzipScripts.ts --outfile=dist/gunzipScripts.js --bundle --minify && esbuild src/gunzipScripts-esm.ts --outfile=dist/gunzipScripts-esm.js --bundle --minify",
"serve": "python3 -m http.server 3000",
"test": "CI=true playwright test",
"test:ui": "playwright test --ui"
},
"dependencies": {
"es-module-lexer": "^1.7.0",
"es-module-shims": "^2.5.1",
"fflate": "^0.7.4"
},
"devDependencies": {
"esbuild": "^0.15.13"
"@playwright/test": "^1.52.0",
"@types/node": "^22.15.21",
"esbuild": "^0.15.18",
"three": "^0.176.0"
}
}
29 changes: 29 additions & 0 deletions packages/gunzip-scripts/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: 0,
// workers: 1,
reporter: process.env.CI ? 'line' : 'html',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
navigationTimeout: 10000,
actionTimeout: 10000,
},

projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],

webServer: {
command: 'npm run serve',
port: 3000,
reuseExistingServer: !process.env.CI,
},
});
28 changes: 28 additions & 0 deletions packages/gunzip-scripts/scripts/create-embedded-shims.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const fs = require('fs');
const { gzipSync } = require('fflate');

// Read the es-module-shims source
const shimsPath = './node_modules/es-module-shims/dist/es-module-shims.js';
const shimsSource = fs.readFileSync(shimsPath, 'utf8');

// Gzip and base64 encode it
const gzipped = gzipSync(new TextEncoder().encode(shimsSource));
const base64 = Buffer.from(gzipped).toString('base64');

// Create the data URI
const dataUri = `data:application/gzip;base64,${base64}`;

console.log('ES Module Shims size:', shimsSource.length, 'bytes');
console.log('Gzipped size:', gzipped.length, 'bytes');
console.log('Base64 size:', base64.length, 'bytes');
console.log('Compression ratio:', Math.round((1 - gzipped.length / shimsSource.length) * 100) + '%');

// Write to a file that can be imported
const output = `// Auto-generated embedded es-module-shims
export const ES_MODULE_SHIMS_GZIPPED = "${dataUri}";
export const ES_MODULE_SHIMS_SIZE = ${shimsSource.length};
export const ES_MODULE_SHIMS_GZIPPED_SIZE = ${gzipped.length};
`;

fs.writeFileSync('./src/embedded-shims.ts', output);
console.log('Written to src/embedded-shims.ts');
4 changes: 4 additions & 0 deletions packages/gunzip-scripts/src/embedded-shims.ts

Large diffs are not rendered by default.

Loading