How do I import a rust WASM module in gatsby js? - rust

I'm trying to use my rust module from the rust webassembly book in my gatsby project. When I try to import the module like so:
import { <rust-struct> } from 'rust_wasm_npm_package';
I get the following error:
The module seem to be a WebAssembly module, but module is not flagged as WebAssembly module for
webpack.
BREAKING CHANGE: Since webpack 5 WebAssembly is not enabled by default and flagged as experimental
feature.
You need to enable one of the WebAssembly experiments via 'experiments.asyncWebAssembly: true' (based
on async modules) or 'experiments.syncWebAssembly: true' (like webpack 4, deprecated).
For files that transpile to WebAssembly, make sure to set the module type in the 'module.rules'
section of the config (e. g. 'type: "webassembly/async"').
(Source code omitted for this binary file)
I'm unable to add the experiments option to the gatsby config file, so I'm not sure what is the best way to import a wasm-pack rust module into gatsby.

I was able to get this working by adding a gatsby-node.js file with the following code:
exports.onCreateWebpackConfig = ({ actions }) => {
actions.setWebpackConfig({
experiments: {
syncWebAssembly: true,
},
});
};
I was then able to import the web assembly asynchronously. Not sure why I did not need to use asyncWebassembly: true instead, but it works!
// The reason for this useless concatenation
// is to get rid of a really specific issue
// with Webpack and WASM modules being imported
// all in one line.
/*eslint no-useless-concat: "off"*/
const module = await import("path/" + "toJSFile.js");
const memModule = await import("path/" + "toWasmModule.wasm");
const memory = memModule.memory;
setMem(memory);

Related

Strange behaviour using legacy packages with ESM modules

I am trying to use the calculate-size package in my app that uses ES modules. This package is very old and declares it's exports using the following code:
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = function() { /* ... */ }
However when I try to import the package as follows I experience some strange behaviour:
// index.mjs
import fn from "calculate-size";
console.log(fn)
I expect the output of this to be that fn is a function however it is an object containing a property named default. It would be possible for me to use fn.default however this would conflict with the package's typings.
I would like to understand some more information about this behaviour as I haven't been able to find any documentation about this issue. I also want to understand if there is a fix on my end of if the only solution is to contact the package maintainer.

Webpack / Vue.js: generate module code at compile-time using ESM dependencies

Environment: webpack 5.44 + vue.js 3.0 + node 12.21
I'm trying to generate a module at compile-time, in order to avoid a costly computation at run-time (as well as 10Mb of dependencies that will never be used except during said computation). Basically run this at compile-time:
import * as BigModule from "big-module";
function extract_info(module) { ... }
export default extract_info(BigModule);
which will be imported at run-time as:
export default [ /* static info */ ];
I tried using val-loader (latest 4.0) which seems designed exactly for this use case.
Problem: big-module is an ESM, but val-loader apparently only supports CJS. So I can neither import ("Cannot use import statement outside a module" error) nor require ("Unexpected token 'export'" error).
Is there any way to make val-loader somehow load the ESM module? Note that I'm not bent on using val-loader, any other technique that achieves the same goal is just as welcome.
After learning way more than I wanted about this issue and node/webpack internals, there seems to be two possible approaches to import ESM from CJS:
Use dynamic import(). But it is asynchronous which makes it unfit here, as val-loader requires a synchronous result.
Transpile the ESM into CJS, which is the approach I took.
In my case, full transpiling is overkill and rewriting imports/exports is sufficient, so I'm using ascjs to rewrite the ESM files, along with eval to safely evaluate the resulting string.
All in all:
// require-esm.js
const fs = require('fs');
const ascjs = require('ascjs');
const _eval = require('eval');
function requireESM(file) {
file = require.resolve(file);
return _eval(ascjs(fs.readFileSync(file)), file, { require: requireESM }, true);
}
module.exports = requireESM;
// val-loader-target.js
const requireESM = require('./require-esm');
const BigModule = requireESM('big-module');
function extract_info(module) { ... }
module.exports = extract_info(BigModule);
Note that:
ascjs is safe to use on CJS modules, since it only rewrites ESM imports/exports. So it's OK for big-module or its dependencies to require CJS files.
the third argument to _eval enables recursive rewriting, otherwise only the top-level file (the one passed to requireESM) is translated.

Jest Mock Globally From a Node Module

I am writing a series of Node modules that require a bunch of common Jest mocks to be setup before I can write unit tests.
I am trying to refactor the unit test setup into a separate module so I can avoid rewriting the setup each time.
The problem is that when I import the following code as a module it no longer mocks the other libraries, while it works just fine when the mocks are set up in a utility file.
Working Code:
jest.mock('#actions/core')
jest.mock('#actions/github')
const { GitHub, context} = require('#actions/github')
const core = require('#actions/core')
GitHub.mockImplementation(() => {
return {
{
repos: {
getContents: jest.fn()
}
}
}
}
module.exports = { core, GitHub, context }
I keep this in a utils.js file next to my test files and import it like const { core, GitHub, context } = require('./utils.js') and everything mocks as I expect. I can run expect().toHaveBeenCalledTimes() and get the numbers I expect.
The problem appears when I move utils.js to another module and require it.
I know that at bottom of the jest documentation it says "...Another file that imports the module will get the original implementation even if it runs after the test file that mocks the module." But I am seeing this work inconsistently with the external file.
Anyone know how to get mocking setup in external modules?

How can I avoid always having to import my own code in Typescript?

So I was recently hacking on a large Typescript project (https://github.com/BabylonJS/Babylon.js) and I noticed that they don't ever have to import anything, they just use their namespace and the rest is (seemingly) magic.
It got me thinking that I would like to use something similar for myself, so I started a simple typescript project to try it out.
tsconfig.json
{
"compilerOptions": {
"baseUrl": "src",
"outFile": "server.js"
}
}
src/main.ts
module Test {
console.log('Main started')
const server:Server = new Server()
}
src/Server.ts
// import * as http from 'http'
module Test {
export class Server {
constructor() {
console.log('Server initialized')
}
}
}
If I build this Typescript project then I get output like the following:
// import * as http from 'http'
var Test;
(function (Test) {
var Server = /** #class */ (function () {
function Server() {
console.log('Server initialized');
}
return Server;
}());
Test.Server = Server;
})(Test || (Test = {}));
var Test;
(function (Test) {
console.log('Main started');
var server = new Test.Server();
})(Test || (Test = {}));
So far, so good. The trouble is that I want to take advantage of some external modules, in this case namely the http module, so I uncomment the import line above and now Typescript reports:
src/server/Server.ts(1,1): error TS6131: Cannot compile modules using option 'outFile' unless the '--module' flag is 'amd' or 'system'.
Node uses the commonjs module system, so obviously setting either of those flags isn't going to help me much. I have none the less tried them as well as various other combinations of flags, but to no avail. I noticed that BabylonJS doesn't really use external imports like that, opting instead to declare them as external and provide them globally at execution time as script tags. Is there maybe an analogue to that for Node?
You can't have these two things at the same time, namely
How can I avoid always having to import my own code in Typescript?
and
I want to take advantage of some external modules
You can avoid imports only by not using external modules, and the result will be one giant script file that can use external dependencies only as globals created by scripts loaded via script tag, as you already noticed.
The language does not allow you to use external modules when you do this. If you have an import of external module at the top level, your file becomes a module and there is no way it could use code from your other files without importing them. And having import of external module inside a namespace is not allowed AFAIK.
That said, I don't think your question - "How can I avoid always having to import my own code in Typescript?" - has a valid premise. CommonJS module system is the solution for preventing large projects from becoming unmaintainable mess. It does not matter if some part of a project is your own code or some external dependency - if it's a separate part with well-defined interface, it should be packaged and consumed as a module.
The solution that worked for me is this:
Server.ts
declare var http: any;
namespace Test {
export class Server {
constructor() {
console.log('Server initialized')
const server = http.createServer()
server.listen()
}
}
}
Then I simply provide http at runtime, for example by prepending a var http = require('http') to the output. Thanks to artem for a nudge in the right direction.

How to use multiple compiled ts files in a node module?

Currently I am trying to use TypeScript to create JavaScript-Files which are then required in a index.js file. I am using VS 2015 Update 3 with node.js tools 1.2 RC. Sadly it is not working like I thought it would.
To begin with here is my initial idea:
I have a node module (to be precise, it is a deployd module http://docs.deployd.com/docs/using-modules/). This module is handling payment providers like paypal or stripe. Now I want to use TypeScript to write interfaces, classes and use types to make it easier to add new payment providers. The old .js files should still be there and used. I want to migrate step by step and use the self-written and compiled .js files together. So I thought I can create .ts files, write my code, save, let VS compile to js and require the compiled js file in another js file. Okay, that is my idea... Now the problem
I have a PaymentProvider.ts file which looks like this
import IPaymentProvider = require("./IPaymentProvider.ts"); // Interface, can't be realized in JavaScript, just TypeScript
export abstract class PaymentProvider implements IPaymentProvider.IPaymentProvider
{
providerName: string;
productInternalId: number;
constructor(providerName : string)
{
this.providerName = providerName;
}
//... some methods
};
The other file is PaypalPaymentProvider.ts
import PaymentProvider = require("./PaymentProvider.ts");
export class PaypalPaymentProvider extends PaymentProvider.PaymentProvider
{
constructor()
{
super("paypal");
}
// more methods
}
VS 2015 doesn't show any errors. The js and .js.map files are generated. Now I thought I could require the files and that's it. I tried to use the PaypalPaymentProvider.js like this const PaypalPaymentProvider = require("./lib/payment-provider/PaypalPaymentProvider.js"); (yes, it is located there) but it's not working. When starting the index.js via node I get the following error:
...\Path\PaymentProvider.ts:1 (function (exports, require, module, __filename, __dirname) { import IPaymentProvider = require("./IPaymentProvider.ts"); Unexpected token import....
I find it strange that this is the error, because JavaScript doesnt't have Interfaces. The compiled IPaymentProvider.js is empty.
Also I thought that TypeScript is mainly for development and the compiled JavaScript for production. So why it is requiring a ts-file? I thought imports in typescript will be converted to require of the compiled js-file?
Do I need to require all compiled js files and not only the one I currently try to use? (I don't think so...)
To be honest, I think the main problem is that I am new to TypeScript and make something wrong from the very beginning.
Any help/advice? Thanks!
I have the solution... Thanks to Paelo's links I was able to see that I need to omit the file ending! So the really simple solution was to write
import IPaymentProvider = require("./IPaymentProvider");
instead of
import IPaymentProvider = require("./IPaymentProvider.ts");
When I changed that in every ts file it worked perfectly!

Resources