httptoolkit / brotli-wasm

A reliable compressor and decompressor for Brotli, supporting node & browsers via wasm
Apache License 2.0
264 stars 21 forks source link

Can't use with Angular #18

Closed daverickdunn closed 2 years ago

daverickdunn commented 2 years ago

I'm trying to load this with an Angular app. TS is happy and the app compiles fine, but at runtime I'm getting an error:

zone.js:1518 Not allowed to load local resource: file:///C:/**/**/node_modules/brotli-wasm/pkg.web/brotli_wasm_bg.wasm

Importing as:

import brotliPromise from 'brotli-wasm';

Angular 13 Chrome 107

pimterry commented 2 years ago

No idea I'm afraid, it sounds like this is an issue with either your deployment setup or your bundler configuration. Your bundler appears to be keeping the reference to the wasm file to load it at runtime (as it should) but if that error is appearing in the browser then it's preserving it with a file:// URL (which is very unusual) and either your browser or server configuration is refusing to allow the request to load this file.

For this to work in browsers, you need to:

It's hard to know every possible configuration I'm afraid! I suspect this isn't really an issue related to this project though - you'll have the same issue with any wasm-based library. If it helps, you can see the Webpack config (for Webpack v5) that this project uses during webpack browser testing here: https://github.com/httptoolkit/brotli-wasm/blob/2ecde28bc3d745b394b20308fe99bec6f9117b68/karma-webpack.conf.js#L26-L56

daverickdunn commented 2 years ago

Hi, thanks for taking a look, that's understandable.

I tried a bunch of stuff to get it to bundle with my app but I couldn't figure out what was wrong. I don't really want to bloat with extra bundling config as I'd have to add extra packages etc. so I've switched to brotli-dec-wasm as that worked without any issues.

Thanks again.

rr923 commented 1 year ago

brotli-dec-wasm

@daverickdunn , hi, does brotli-dec-wasm work for you with angular? I tried and it shows the same error. Could you please share how you did that?

myl7 commented 1 year ago

it shows the same error

As the error comes from the web variant (pkg.web), it is because the code generated by wasm-pack uses new URL('index_bg.wasm', import.meta.url) to determine to the URL to the WASM binary file so that in the browser it can fetch and load it. The reason why it breaks in Angular may be that import.meta.url is not an expected value. I am not familiar with Angular so I am not so sure though.

If you are looking for a quick and easy solution, you can just serve the WASM file on your own (by making the bundler copy it to the output dir, or even manually) and pass the URL string to the init function (__wbg_init in the generated code, pkg.web/brotli_wasm.js).

In brotli-wasm it only exports init().then(() => brotliWasm) so it can be a little hard to figure out how. In brotli-dec-wasm I will soon release a new version so that you can do this:

import { init } from 'brotli-dec-wasm'

const wasmUrl = '...'
const brotli = await init(wasmUrl)

Or refer to the asset.md document.

PS: I am the author of brotli-dec-wasm and also put effort into this package so that Brotli can be easier in JS.

And @pimterry , I are recently working on another kind of entry for the web environment of these packages with less "side-effects". It is in asset.js with another export in package.json, the document is at asset.md, and an example is available at example/webpack-asset. Do you think it is fine and can I open a PR for the new entry?

daverickdunn commented 1 year ago

@rr923

Note that I'm encoding the brotli buffer as base64, you may have it encoded as something else.

import { Injectable } from '@angular/core';
import { catchError, map, ReplaySubject, throwError } from 'rxjs';

function Base64ToArrayBuffer(base64: string) {
    const binary_string = atob(base64);
    const len = binary_string.length;
    const bytes = new Uint8Array(len);
    for (let i = 0; i < len; i++) {
        bytes[i] = binary_string.charCodeAt(i);
    }
    return bytes;
}

@Injectable({
    providedIn: 'root'
})
export class BrotliService {

    public brotli = new ReplaySubject<any>();

    private decoder = new TextDecoder();

    constructor() {
        this.init();
    }

    private async init() {
        const brotli = await import('brotli-dec-wasm');
        this.brotli.next(brotli)
    }

    public decompress<T = any>(data: string) {
        return this.brotli.pipe(
            map(brotli => {

                const buff_array = Base64ToArrayBuffer(data); // base64 string to buffer (compressed)

                const decompressed = brotli.brotliDec(buff_array); // compressed buffer to uncompressed buffer

                const decoded = this.decoder.decode(decompressed); // uncompressed buffer to utf-8 string

                return JSON.parse(decoded) as T;

            }),
            catchError(error => {
                console.error(error)
                return throwError(() => new Error(error))
            })
        )
    }

}