Skip to content

Commit 1439a56

Browse files
committed
Add web Next.js stream compress example
1 parent 4779827 commit 1439a56

File tree

9 files changed

+1087
-0
lines changed

9 files changed

+1087
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# next.js
12+
/.next/
13+
/out/
14+
15+
# production
16+
/build
17+
18+
# misc
19+
.DS_Store
20+
*.pem
21+
22+
# debug
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
27+
# local env files
28+
.env*.local
29+
30+
# vercel
31+
.vercel
32+
33+
# typescript
34+
*.tsbuildinfo
35+
next-env.d.ts
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2+
3+
## Getting Started
4+
5+
First, run the development server:
6+
7+
```bash
8+
npm run dev
9+
# or
10+
yarn dev
11+
# or
12+
pnpm dev
13+
```
14+
15+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
16+
17+
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
18+
19+
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
20+
21+
## Learn More
22+
23+
To learn more about Next.js, take a look at the following resources:
24+
25+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
26+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
27+
28+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
29+
30+
## Deploy on Vercel
31+
32+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
33+
34+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Inter } from 'next/font/google'
2+
3+
const inter = Inter({ subsets: ['latin'] })
4+
5+
export const metadata = {
6+
title: 'TransformStream example',
7+
}
8+
9+
export default function RootLayout({ children }: { children: React.ReactNode }) {
10+
return (
11+
<html lang="en">
12+
<head>
13+
<link rel="stylesheet" href="https://unpkg.com/mvp.css" />
14+
<script src="https://unpkg.com/web-streams-polyfill/dist/polyfill.min.js"></script>
15+
</head>
16+
<body className={inter.className}>{children}</body>
17+
</html>
18+
)
19+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
'use client'
2+
3+
import streamSaver from 'streamsaver'
4+
5+
import { BrotliCompressTransformStream } from './utils'
6+
7+
export default function Home() {
8+
const brotli = (op: string) => async () => {
9+
const brotliWasm = await (await import('brotli-wasm')).default
10+
switch (op) {
11+
case 'enc':
12+
const fileInput = document.querySelector('#file-input') as HTMLInputElement
13+
const file = fileInput.files![0]
14+
if (!file) throw new Error('No file selected')
15+
const inputStream = file.stream()
16+
const outputStream = streamSaver.createWriteStream(file.name + '.br')
17+
// 1KB chunks
18+
const transformStream = new BrotliCompressTransformStream(brotliWasm, 1024)
19+
inputStream.pipeThrough(transformStream).pipeTo(outputStream)
20+
break
21+
case 'dec':
22+
console.error('Not implemented')
23+
break
24+
}
25+
}
26+
27+
return (
28+
<main>
29+
<h1>TransformStream example</h1>
30+
<div>
31+
<input type="file" id="file-input" />
32+
<div>
33+
<button onClick={brotli('enc')}>Compress</button>
34+
</div>
35+
<div>
36+
<button disabled onClick={brotli('dec')}>
37+
Decompress
38+
</button>
39+
</div>
40+
</div>
41+
</main>
42+
)
43+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// The structure is from the MDN docs: https://developer.mozilla.org/en-US/docs/Web/API/TransformStream
2+
3+
import type { CompressStream, BrotliWasmType } from 'brotli-wasm'
4+
5+
interface BrotliCompressTransformer extends Transformer<Uint8Array, Uint8Array> {
6+
brotliWasm: BrotliWasmType
7+
outputSize: number
8+
stream: CompressStream
9+
}
10+
11+
const brotliCompressTransformerBuilder: (
12+
brotliWasm: BrotliWasmType,
13+
outputSize: number,
14+
quality?: number
15+
) => BrotliCompressTransformer = (brotliWasm, outputSize, quality) => ({
16+
brotliWasm,
17+
outputSize,
18+
stream: new brotliWasm.CompressStream(quality),
19+
start() {},
20+
transform(chunk, controller) {
21+
do {
22+
const inputOffset = this.stream.last_input_offset()
23+
const input = chunk.slice(inputOffset)
24+
const output = this.stream.compress(input, this.outputSize)
25+
controller.enqueue(output)
26+
} while (this.stream.result() === brotliWasm.BrotliStreamResult.NeedsMoreOutput)
27+
if (this.stream.result() !== brotliWasm.BrotliStreamResult.NeedsMoreInput) {
28+
controller.error(`Brotli compression failed when transforming with error code ${this.stream.result()}`)
29+
}
30+
},
31+
flush(controller) {
32+
do {
33+
const output = this.stream.compress(undefined, this.outputSize)
34+
controller.enqueue(output)
35+
} while (this.stream.result() === brotliWasm.BrotliStreamResult.NeedsMoreOutput)
36+
if (this.stream.result() !== brotliWasm.BrotliStreamResult.ResultSuccess) {
37+
controller.error(`Brotli compression failed when flushing with error code ${this.stream.result()}`)
38+
}
39+
},
40+
})
41+
42+
export class BrotliCompressTransformStream extends TransformStream<Uint8Array, Uint8Array> {
43+
constructor(brotliWasm: BrotliWasmType, outputSize: number, quality?: number) {
44+
super(brotliCompressTransformerBuilder(brotliWasm, outputSize, quality))
45+
}
46+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/** @type {import('next').NextConfig} */
2+
const nextConfig = {
3+
reactStrictMode: true,
4+
}
5+
6+
function withWebpack(nextConfig) {
7+
return Object.assign({}, nextConfig, {
8+
webpack(config) {
9+
// For WASM
10+
config.experiments = {
11+
...config.experiments,
12+
asyncWebAssembly: true,
13+
}
14+
15+
if (typeof nextConfig.webpack == 'function') {
16+
return nextConfig.webpack(config, options)
17+
}
18+
return config
19+
},
20+
})
21+
}
22+
23+
module.exports = withWebpack(nextConfig)

0 commit comments

Comments
 (0)