I'm trying to access a exported Cache into another file, but not having success.
Basically I've got two files:
cache.ts I'll expose only the part that meter
import Cache = require('node-cache');
const narrativeCache: Cache = New Cache();
protected setNarrativeCache(): void {
narrativeCache.set(123,'abc',0);
}
module.exports.narrativeCache = narrativeCache;
I want use this narrative in this other file:
module.ts
import { narrativeCache } from '../transport/cache.ts';
function test1(): void {
narrativeCache.get(123); //Error in here, it doesn't find this
}
Files tree:
src\transport\cache.ts
src\reader\module.ts
The error is because it doesn't find this narrativeCache.
The error was becausing of typescript export type:
Here's the solution
export const narrativeCache: Cache = new Cache();
Related
We have a gRPC repo that has a JS implementation of creating a client.
/**
* Code generated by protoc-gen-twirp_js v5.0.0, DO NOT EDIT.
* source: domain_service/service.proto
*/
// import our twirp js library dependency
var createClient = require("twirp");
// import our protobuf definitions
var pb = require("./domain_service/service_pb.js");
Object.assign(module.exports, pb);
/**
* Creates a new DomainClient
*/
module.exports.createDomainClient = function(baseurl, extraHeaders, useJSON) {
var rpc = createClient(baseurl, "events.domain_service.Domain", "v5.0.0", useJSON, extraHeaders === undefined ? {} : extraHeaders);
return {
fireDomainEvent: function(data) { return rpc("FireDomainEvent", data, pb.FireDomainEventResponse); }
}
}
I created a d.ts file in the same folder using TS playground
export function createDomainClient(
baseurl: any,
extraHeaders: any,
useJSON: any
): {
fireDomainEvent: (data: any) => any;
};
But when I try to implement the code:
import { createDomainClient } from '#MyOrganization/package';
const client = new createDomainClient(
'https://my.url.com', '', false
);
I get the follow error: 'new' expression, whose target lacks a construct signature, implicitly has an 'any' type.ts(7009)
I am simply trying to import the commonJS client into my Typescript project with making the least amount of changes on the gRPC repo side.
Either explicitly set any type for client
import { createDomainClient } from '#MyOrganization/package';
const client: any = new createDomainClient(
'https://my.url.com', '', false
);
Or add a construct signature to the type
How does interfaces with construct signatures work?
Let's say we are creating a module called app by constructing a new vm.SourceTextModule object:
const context = {
exports: {},
console, // custom console object
};
const sandbox = vm.createContext(context);
const app = new vm.SourceTextModule(
`import path from 'path';
console.log(path.resolve('./src'));`,
{
context: sandbox,
}
);
According to the Node.js documentation to obtain the default export from path module we should "link" the imported dependencies of app module to it.
To achieve this we should pass linker callback to app.link method:
async function linker(specifier, referencingModule) {
// the desired logic...
}
await app.link(linker);
How to implement linker function properly so that we could import path module in newly created app module and use it:
await app.evaluate(); // => /home/user/Documents/project/src
P.S. We are using TypeScript, so I checked if we have installed types for path package.
package.json:
"#types/node": "^17.0.31",
I found https://github.com/nodejs/node/issues/35848 where someone posted a code snippet.
From there I've adapted the following linker callback:
const imports = new Map();
async function linker(specifier, referencingModule) {
if (imports.has(specifier))
return imports.get(specifier);
const mod = await import(specifier);
const exportNames = Object.keys(mod);
const imported = new vm.SyntheticModule(
exportNames,
() => {
// somehow called with this === undefined?
exportNames.forEach(key => imported.setExport(key, mod[key]));
},
{ identifier: specifier, context: referencingModule.context }
);
imports.set(specifier, imported);
return imported;
}
The code snippet from the GitHub issue didn't work for me on Node 18.7.0 as is, because the evaluator callback passed to the constructor of SyntheticModule is somehow called with this set to undefined. This may be a Node bug.
I also cached the imported SyntheticModules in a Map because if they have internal state, creating a new SyntheticModule every time will reset that state.
I need to import a JavaScript module from an in memory variable.
I know that this can be done using SystemJS and Webpack.
But nowhere can I find a good working example nor documentation for the same. The documentations mostly talks of dynamic import of .js files.
Basically I need to import the module like below
let moduleData = "export function doSomething(string) { return string + '-done'; };"
//Need code to import that moduleData into memory
If anyone can point to documentation that will be great
There are limitations in the import syntax that make it difficult to do if not impossible without using external libraries.
The closest I could get is by using the Dynamic Import syntax. Two examples follow: the first one is the original I wrote while the second one is an edit from a user that wanted to modernize it.
The original one:
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<script>
var moduleData = "export function hello() { alert('hello'); };";
var b64moduleData = "data:text/javascript;base64," + btoa(moduleData);
</script>
<script type="module">
async function doimport() {
const module = await import(b64moduleData);
module.hello();
}
doimport();
</script>
</body>
</html>
The modernized one:
function doimport (str) {
if (globalThis.URL.createObjectURL) {
const blob = new Blob([str], { type: 'text/javascript' })
const url = URL.createObjectURL(blob)
const module = import(url)
URL.revokeObjectURL(url) // GC objectURLs
return module
}
const url = "data:text/javascript;base64," + btoa(moduleData)
return import(url)
}
var moduleData = "export function hello() { console.log('hello') }"
var blob = new Blob(["export function hello() { console.log('world') }"])
doimport(moduleData).then(mod => mod.hello())
// Works with ArrayBuffers, TypedArrays, DataViews, Blob/Files
// and strings too, that If URL.createObjectURL is supported.
doimport(blob).then(mod => mod.hello())
You will however notice that this has some limitations on the way the import code is constructed, which may not precisely match what you need.
The simplest solution is probably to send the code of the module on the server for it to generate a temporary script to be then imported using a more conventional syntax.
let moduleData = await import("data:text/javascript,export function doSomething(str) { return str + '-done'; }");
and to test it
moduleData.doSomething('test');
Use nodejs flag --experimental-modules to use import ... from ...;
node --experimental-modules index.mjs
index.mjs:
import fs from 'fs';
let fileContents = "export function doSomething(string) { return string + '-done'; };"
let tempFileName = './.__temporaryFile.mjs';
fs.writeFileSync(tempFileName, fileContents);
console.log('Temporary mjs file was created!');
import(tempFileName)
.then((loadedModule) => loadedModule.doSomething('It Works!'))
.then(console.log)
Further reading here
How It Works:
I first create the file with fs.writeFileSync
then I use import method's promise to load module and
pipe doSomething method call with "It Works!"
and then log the result of doSomething.
Credits: https://stackoverflow.com/a/45854500/3554534, https://v8.dev/features/dynamic-import, #Louis
NodeJS:
// Base64 encode is needed to handle line breaks without using ;
const { Module } = await import(`data:text/javascript;base64,${Buffer.from(`export class Module { demo() { console.log('Hello World!') } }`).toString(`base64`)}`)
let instance = new Module()
instance.demo() // Hello World!
You can create component and Module on fly. But not from string. Here is an example:
name = "Dynamic Module on Fly";
const tmpCmp = Component({
template:
'<span (click)="doSomething()" >generated on the fly: {{name}}</span>'
})(
class {
doSomething(string) {
console.log("log from function call");
return string + "-done";
}
}
);
const tmpModule = NgModule({ declarations: [tmpCmp] })(class {});
this._compiler.compileModuleAndAllComponentsAsync(tmpModule).then(factories => {
const f = factories.componentFactories[0];
const cmpRef = this.container.createComponent(f);
cmpRef.instance.name = "dynamic";
});
Here is the stackblitz
Based on Feroz Ahmed answer, here is a specific example to dynamically load a Vue 3 component from the server via socket.io.
Server-side (Node + socket.io)
socket.on('give-me-component', (data: any, callback: any) => {
const file = fs.readFileSync('path/to/js/file', 'utf-8');
const buffer = Buffer.from(file).toString('base64');
callback(`data:text/javascript;base64,${buf}`);
});
Client-side (Vue 3 + socket.io-client)
socket.emit('give-me-component', null, async (data: any) => {
// Supposes that 'component' is, for example, 'ref({})' or 'shallowRef({})'
component.value = defineAsyncComponent(async () => {
const imp = await import(data);
// ... get the named export you're interested in, or default.
return imp['TheNamedExport_or_default'];
});
});
...
<template>
<component :is="component" />
</template>
For some weird reason, the suggested way of dynamically import()-ing a "data" URL throws an error: TypeError: Invalid URL when done from an npm package code.
The same "data" URL import()s without any errors from the application code though.
Weird.
const module = await import(`data:text/javascript;base64,${Buffer.from(functionCode).toString(`base64`)}`)
<script type="module">
import { myfun } from './myModule.js';
myfun();
</script>
/* myModule.js */
export function myfun() {
console.log('this is my module function');
}
Consider the following code:
import redis = require('redis'); //Has ambient declaration from DT
import bluebird = require('bluebird'); //Has ambient declaration from DT
bluebird.promisifyAll((<any>redis).RedisClient.prototype);
bluebird.promisifyAll((<any>redis).Multi.prototype);
const client = redis.createClient();
client.getAsync('foo').then(function(res) {
console.log(res);
});
getAsync will error out because it's created on the fly and not defined in any .d.ts file. So what is the proper way to handle this?
Also, even though I have the .d.ts files loaded for redis, I still need to cast redis to any to be used for promisifyAll. Otherwise, it will spill out error:
Property 'RedisClient' does not exist on type 'typeof "redis"'
Is typing it to any the only easy way to go?
I'm solving this by declaration merging the setAsync & getAsync methods. I added the following code in my own custom .d.ts file.
declare module "redis" {
export interface RedisClient extends NodeJS.EventEmitter {
setAsync(key:string, value:string): Promise<void>;
getAsync(key:string): Promise<string>;
}
}
Another way to do it which requires less code is to extend the Redis object like so:
import { promisify } from 'util';
import { ClientOpts, RedisClient } from 'redis';
class AsyncRedis extends RedisClient {
public readonly getAsync = promisify(this.get).bind(this);
public readonly setAsync = promisify(this.set).bind(this);
public readonly quitAsync = promisify(this.quit).bind(this);
public readonly rpushAsync: (list: string, item: string) => Promise<number> = promisify(
this.rpush
).bind(this);
public readonly blpopAsync: (
list: string,
timeout: number
) => Promise<[string, string]> = promisify(this.blpop).bind(this);
public readonly flushdbAsync = promisify(this.flushdb).bind(this);
}
Notice that not all method signatures overwrite correctly, so you have to help typescript a little.
Now you can just use this enhanced class by creating it with your options, for example:
new AsyncRedis({
host: process.env.REDIS_HOST || '127.0.0.1',
password: process.env.REDIS_PASSWORD || 'whatever',
});
Just adding to Dave's answer, in my needs, I has to add in Multi for atomic operations.
declare module 'redis' {
export interface RedisClient extends NodeJS.EventEmitter {
execAsync(...args: any[]): Promise<any>;
hgetallAsync(...args: any[]): Promise<any>;
// add other methods here
}
export interface Multi extends Commands<Multi> {
execAsync(...args: any[]): Promise<any>;
// add other methods here
}
}
This solution works fine for me:
import { promisifyAll } from 'bluebird'; // import here works only if #types/bluebird is installed
import redis, { RedisClient, Multi } from 'redis'; // import here works only if #types/redis is installed
// Convert Redis client API to use promises, to make it usable with async/await syntax
const MultiAsync: any = promisifyAll(Multi.prototype);
const RedisClientAsync: any = promisifyAll(RedisClient.prototype);
const redisAsync = { ...redis, Multi: MultiAsync, RedisClient: RedisClientAsync };
const client: typeof RedisClientAsync = redisAsync.createClient();
// now you can use client async methods, i.e. client.getAsync, client.hgetAsync, client.hsetAsync, client.expireAsync...
I am looking to create a Modules system within my Application. I'm unsure on how to implement the following:
Module 1
class SomethingModule {
}
export default SomethingModule;
Module 2
class SomethingElseModule {
}
export default SomethingElseModule;
Module Loader
class ModuleLoader {
load(module) {
// "module" could be "Something" which should create a
// new instance of "SomethingModule"
module = module + 'Module';
// Require and create an instance of "module"
}
}
export default ModuleLoader;
Main File
var ModuleLoader = require('./ModuleLoader');
var loader = new ModuleLoader();
loader.load('SomethingElse');
I'm pretty new to modularised JavaScript and have no idea if this is even possible/feasible. If not, is there a way of doing this you'd suggest without polluting the global namespace and referencing window[moduleName]?
If I understand well your problem, you can create a Map and load your module in there:
class ModuleLoader {
constructor() {
this._modules = new Map();
}
load(module) {
// you should check if the module exist in a good world to not get any error
// and probably adjust the path of what you are requiring
this._modules.set(module, new require(`${module}Module`));
}
getModuleByName(name) {
return this._modules.get(name);
}
}
Edit: To make it work with browserify, you will need to have this list of all the modules somewhere. You can create a separate file ModuleBag like this:
//moduleBag.js
import yourModule from './yourModule';
import yourSecondModule from './yourSecondModule';
var map = new Map();
map.set('yourModule', yourModule);
map.set('yourSecondModule', yourSecondModule);
export default class map;
//moduleLoader.js
import moduleBag from './moduleBag';
class ModuleLoader {
constructor() {
this._modules = new Map();
}
load(module) {
var mod = moduleBag.get(module);
this._modules.set(module, new mod());
}
getModuleByName(name) {
return this._modules.get(name);
}
}
or just put it in the same file.
With this, browserify will be able to load all the necessary files.