Home > Mobile >  JSInterop compiled with webpack is undefined in blazor
JSInterop compiled with webpack is undefined in blazor

Time:11-21

I have a Blazor webassembly application, in which I am trying to include typescript/javascript code using JSInterop, which fails, when the js code is created by webpack during build.

When creating a simple hello-world.js file in my wwwroot with the following content:

export function showAlertSimple() {
    alert("hello world");
}

I can invoke it very easily using:

public class HelloWorld : JSModule
{
    public HelloWorld(IJSRuntime js) 
        : base(js, "./js/hello-world.js")
    {
    }

    public async Task ShowAlert() => await InvokeVoidAsync("showAlert");
}

If I try to do the same with a typescript file, which I created in a different place and then let it compile down to ES6 with webpack, put it into the same folder, blazor is unable to call the method:

./NpmJs/src/hello-world2.ts

export function showAlertFromWebPack() {
    alert("Hello World from webpack");
}

./NpmJs/webpack.config.js

module.exports = {
    entry: {
        helloworld2: './src/hello-world2.ts',
    },
    module: {
        rules: [
            {
                test: /\.ts?$/,
                use: 'ts-loader',
                exclude: /node_modules/,
            },
        ],
    },
    resolve: {
        extensions: ['.tsx', '.ts', '.js'],
    },
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, '../../wwwroot/js'),
    },
    devtool: 'source-map'
};
public class HelloWorld2 : JSModule
{
    public HelloWorld2(IJSRuntime js)
        : base(js, "./js/helloworld2.bundle.js")
    {
    }

    public async Task ShowAlert() => await InvokeVoidAsync("showAlertFromWebPack");
}

This then throws the following exception in the browsers console:

crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: Could not find 'showAlertFromWebPack' ('showAlertFromWebPack' was undefined).
      Error: Could not find 'showAlertFromWebPack' ('showAlertFromWebPack' was undefined).

The file itself is loaded correctly. I can see it in the source tab.

What am I missing here?

For completeness sake, here is the JSModule class, which I have shamelessly plugged from Steven Sanderson over at github.(https://github.com/SteveSandersonMS/AudioBrowser/blob/dd7a03d93b4de5e97efb333f1120792ee48c70ba/MediaFilesAPI/Util/JSModule.cs#L7)

public abstract class JSModule : IAsyncDisposable
{
    private readonly Task<IJSObjectReference> moduleTask;

    // On construction, we start loading the JS module
    protected JSModule(IJSRuntime js, string moduleUrl)
        => moduleTask = js.InvokeAsync<IJSObjectReference>("import", moduleUrl).AsTask();

    // Methods for invoking exports from the module
    protected async ValueTask InvokeVoidAsync(string identifier, params object[]? args)
        => await (await moduleTask).InvokeVoidAsync(identifier, args);
    protected async ValueTask<T> InvokeAsync<T>(string identifier, params object[]? args)
        => await (await moduleTask).InvokeAsync<T>(identifier, args);

    // On disposal, we release the JS module
    public async ValueTask DisposeAsync()
        => await (await moduleTask).DisposeAsync();
}

CodePudding user response:

I found 2 working solutions. One using webpack, the other by ditching webpack and replacing it by vitejs.

Using webpack we need to enable experiments.outputModule (https://webpack.js.org/configuration/experiments/#experimentsoutputmodule) and set output.library.type to module (https://webpack.js.org/configuration/output/#outputlibrarytype):

The final config would then look like this:

module.exports = {
    entry: {
        helloworld2: './src/hello-world2.ts',
    },
    module: {
        rules: [
            {
                test: /\.ts?$/,
                use: 'ts-loader',
                exclude: /node_modules/,
            },
        ],
    },
    resolve: {
        extensions: ['.tsx', '.ts', '.js'],
    },
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, '../../wwwroot/js'),
        library: {
            type: "module",
        },
    },
    experiments: {
       outputModule: true
    },
    devtool: 'source-map'
};

Using vitejs the vite.config.js, the config would looke like this:

import * as path from 'path';
import { defineConfig } from "vite";

export default defineConfig({
   appType: 'mpa',
   build: {
      target: 'esnext',
      outDir: '../wwwroot/js',
      lib: {
         entry: path.resolve(__dirname, './src/index.ts'),
         name: "YourPackageName",
         fileName: (format) => `your-package-name.${format}.js`,
      },
   }
});

Vite itself will then produce 2 files:

  • your-package-name.es.js
  • your-package-name.umd.js

You need to link / use the es file inside blazor and you're good to go.

  • Related