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.