Compiling to ES6? - haxe

How would I compile
export default User
import socket from "./socket"
this ES6 javascript function with haxe?
import socket from "./socket"
let User = {
init(socket, element) {
if (!element) {
return
}
let userId = element.getAttribute("data-id")
userId= Math.random()
socket.connect()
this.onReady(userId, socket)
}
}
export default User

I actually had the same need a while ago, in order to integrate with Ember 2.0/Ember CLI. I didn't find a way with pure Haxe, the only alternative is to either:
1) Build a custom js generator - clunky because you lose the goodies of the built-in js generator since there's no granular control over what features you use - it's all or nothing - i.e you can't only change the output of a certain expression/type in the AST, and you can't reference the built-in generator and delegate to it when needed.
2) A pre-processor that parses the hx file, removes the ES6 code, compiles the file, and adds the ES6 code back, clunky as well, but might work.
3) Hack the OCAML code for the compiler and add some kind of class-level metadata tag, something like #:ESImport("import {foo} from bar"), #:ESExport("export default foo"). This could also be done with #1 (custom js gen), but by modifying the OCaml code, you get to keep the built-in js gen.
I've given up on integrating Haxe code with ES6 for now, I wish Haxe had better build-in support for ES6 (i.e an ES2016 generator) or more granular hooks for the JS Custom generator API.
As a reference, here's my message to the Haxe mailing list, about this very issue: https://groups.google.com/forum/#!topic/haxelang/jSTkkaNgfB8.

As of 4.0.0-rc.2, Haxe now supports generation of ES6 classes with the -D je-es=6 flag.
With that, this example...
class Main {
static function main() {
trace("Hello World");
}
}
...produces the following JavaScript code:
// Generated by Haxe 4.0.0-rc.2+43ed6c9b4
(function ($global) { "use strict";
class Main {
static main() {
console.log("source/Main.hx:3:","Hello World");
}
}
Main.main();
})({});
//# sourceMappingURL=run.js.map
Further ES6 support is planned:
ES6 types
ES6 module exports
Code Splitting

Related

Create a TypeScript library with optional dependencies resolved by application

I've written a library published to a private npm repo which is used by my applications.
This library contains utilities and has dependencies to other libraries, as an example let's choose #aws-sdk/client-lambda.
Some of my applications use only some of the utilities and don't need the dependencies to the external libraries, while some applications use all of the utilities.
To avoid having all applications getting a lot of indirect dependencies they don't need, I tried declaring the dependencies as peerDependencies and having the applications resolve the ones they need. It works well to publish the package, and to use it from applications who declare all of the peerDependencies as their own local dependencies, but applications failing to declare one of the dependencies get build errors when the included .d.ts files of the library are imported in application code:
error TS2307: Cannot find module '#aws-sdk/client-kms' or its corresponding type declarations.
Is it possible to resolve this situation so that my library can contain many different utils but the applications may "cherry-pick" the dependencies they need to fulfill the requirements of those utilities in runtime?
Do I have to use dynamic imports to do this or is there another way?
I tried using #ts-ignore in the library code, and it was propagated to the d.ts file imported by the applications, but it did not help.
Setup:
my-library
package.json:
peerDependencies: {
"#aws-sdk/client-lambda": "^3.27.0"
}
foo.ts:
import {Lambda} from '#aws-sdk/client-lambda';
export function foo(lambda: Lambda): void {
...
}
bar.ts:
export function bar(): void {
...
}
index.ts:
export * from './foo';
export * from './bar';
my-application1 - works fine
package.json:
dependencies: {
"my-library": "1.0.0",
"#aws-sdk/client-lambda": "^3.27.0"
}
test.ts:
import {foo} from 'my-library';
foo();
my-application2 - does not compile
package.json:
dependencies: {
"my-library": ...
}
test:ts:
import {bar} from 'my-library';
bar();
I found two ways of dealing with this:
1. Only use dynamic imports for the optional dependencies
If you make sure that types exported by the root file of the package only include types and interfaces and not classes etc, the transpiled JS will not contain any require statement to the optional library. Then use dynamic imports to import the optional library from a function so that they are required only when the client explicitly uses those parts of the library.
In the case of #aws-sdk/client-lambda, which was one of my optional dependencies, I wanted to expose function that could take an instance of a Lambda object or create one itself:
import {Lambda} from '#aws-sdk/client-lambda';
export function foo(options: {lambda?: Lambda}) {
if (!lambda) {
lambda = new Lambda({ ... });
}
...
}
Since Lambda is a class, it will be part of the transpiled JS as a require statement, so this does not work as an optional dependency. So I had to 1) make that import dynamic and 2) define an interface to be used in place of Lambda in my function's arguments to get rid of the require statement on the package's root path. Unfortunately in this particular case, the AWS SDK does not offer any type or interface which the class implements, so I had to come up with a minimal type such as
export interface AwsClient {
config: {
apiVersion: string;
}
}
... but of course, lacking a type ot represent the Lambda class, you might even resort to any.
Then comes the dynamic import part:
export async function foo(options: {lambda?: AwsClient}) {
if (!lambda) {
const {Lambda} = await import('#aws-sdk/client-lambda');
lambda = new Lambda({ ... });
}
...
}
With this code, there is no longer any require('#aws-sdk/client-lambda') on the root path of the package, only within the foo function. Only clients calling the foo function will have to have the dependency in their
node_modules.
As you can see, a side-effect of this is that every function using the optional library must be async since dynamic imports return promises. In my case this worked out, but it may complicate things. In one case I had a non-async function (such as a class constructor) needing an optional library, so I had no choice but to cache the promised import and resolve it later when used from an async member function, or do a lazy import when needed. This has the potential of cluttering code badly ...
So, to summarize:
Make sure any code that imports code from the optional library is put inside functions that the client wanting to use that functionality calls
It's OK to have imports of types from the optional library in the root of your package as it's stripped out when transpiled
If needed, defined substitute types to act as place-holders for any class arguments (as classes are both types and code!)
Transpile and investigate the resulting JS to see if you have any require statement for the optional library in the root, if so, you've missed something.
Note that if using webpack etc, using dynamic imports can be tricky as well. If the import paths are constants, it usually works, but building the path dynamically (await import('#aws-sdk/' + clientName)) will usually not unless you give webpack hints. This had me puzzled for a while since I wrote a wrapper in front of my optional AWS dependencies, which ended up not working at all for this reason.
2. Put the files using the optional dependencies in .ts files not exported by the root file of the package (i.e., index.ts).
This means that clients wanting to use the optional functionality must
import those files by sub-path, such as:
import {OptionalStuff} from 'my-library/dist/optional;
... which is obviously less than ideal.
in my case, the typescript IDE in vscode fails to import the optional type, so im using the relative import path
// fix: Cannot find module 'windows-process-tree' or its corresponding type declarations
//import type * as WindowsProcessTree from 'windows-process-tree';
import type * as WindowsProcessTree from '../../../../../node_modules/#types/windows-process-tree';
// global variable
let windowsProcessTree: typeof WindowsProcessTree;
if (true) { // some condition
windowsProcessTree = await import('windows-process-tree');
windowsProcessTree.getProcessTree(rootProcessId, tree => {
// ...
});
}
package.json
{
"devDependencies": {
"#types/windows-process-tree": "^0.2.0",
},
"optionalDependencies": {
"windows-process-tree": "^0.3.4"
}
}
based on vscode/src/vs/platform/terminal/node/windowsShellHelper.ts

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.

Whether we should use classes in Typescript for Node or the same way as we are doing in javascript exporting functions development

I have a concern regarding Node development in Typescript.
I have been working with a node developer who is proficient in node with javascript but now we have decided to move from javascript to typescript, which is a good decision.
The problem is as we have started writing code in typescript but as we are facing conflicts to choose should we use modular way in typescript or use class.
Option 1:
ab.ts
export function a () {
will write whole code here for that
}
Option 2:
ab.ts
export class Ab {
constructor(){
}
public a() {
//write code here
}
}
If you use TypeScript and don't use Class / Interface then you are not getting the most out of it. So, I would highly recommend to structure your project in such way that you can make use of class/interface.

How to export typings along with a module?

I'm writing a module that adds some functionalities to testing suites our team is using. It's a project that has main typescript file, that wraps other exports from our project.
index.d.ts:
export * from "./OneModule"
export * from "./AnotherModule"
One of the modules has a functionality of adding new methods to Chai.js assertion library. This is achieved by using it's functions for adding new methods, for example:
assertion.addChainableMethod('newMethod', callback, chainCallback)
will add ability to write statements like expect(something).to.be.newMethod(). TypeScript though, doesn't recognize those new methods, since the type Assertion in #types/chai obviously doesn't have those functions.
I've created interface definition to be merged with the Chai Assertion like this:
declare namespace Chai {
interface Assertion {
newMethod(something: string): Assertion;
newMethod: Assertion;
}
}
The addChainableMethod adds new method so it can be also chaines as expect(something).to.be.newMethod.equals() that's why it has to be defined both as property and method.
The problem is, no matter how I add the above declaration to index.d.ts the new assertions aren't visible in the importing project. I suspect it's somehow wrapped in module namespace(but I might be very wrong about that). I know I can do #types/newAssertions and define them there, but that requires users to include 2 projects in package.json, and also requires our developers to handle 2 projects at once. How can I export both my Chai namespace extension and all the modules I have?
The best I've managed to do is something like that:
declare module 'my-module' {
function AddNewAssertions(chai: any, utils: any): void;
}
declare namespace Chai {
interface Assertion { //same as above }
}
But with this I cannot expose my other modules, and it works only when I'm only exposing AddNewAssertions function, which adds those assertions. This works perfectly though.
I knocked up a very basic test app for this, and I had some success with adding:
declare global {
namespace Chai {
interface Assertion {
newMethod(something: string): Assertion;
newMethod: Assertion;
}
}
}
This makes the extension visible in your other modules, not just the one with the declaration.

RequireJS Dynamic Paths Replacement

I have a requirejs module which is used as a wrapper to an API that comes from a different JS file:
apiWrapper.js
define([], function () {
return {
funcA: apiFuncA,
funcB: apiFuncB
};
});
It works fine but now I have some new use cases where I need to replace the implementation, e.g. instead of apiFuncA invoke my own function. But I don't want to touch other places in my code, where I call the functions, like apiWrapper.funcA(param).
I can do something like the following:
define([], function () {
return {
funcA: function(){
if(regularUseCase){
return apiFuncA(arguments);
} else {
return (function myFuncAImplementation(params){
//my code, instead of the external API
})(arguments);
}
},
funcB: apiFuncB
};
});
But I feel like it doesn't look nice. What's a more elegant alternative? Is there a way to replace the module (apiWrapper) dynamically? Currently it's defined in my require.config paths definition. Can this path definition be changed at runtime so that I'll use a different file as a wrapper?
Well, first of all, if you use Require.js, you probably want to build it before production. As so, it is important you don't update paths dynamically at runtime or depends on runtime variables to defines path as this will prevent you from running r.js successfully.
There's a lot of tools (requirejs plugins) out there that can help you dynamically change the path to a module or conditionnaly load a dependency.
First, you could use require.replace that allow you to change parts (or all) of a module URL depending on a check you made without breaking the build.
If you're looking for polyfilling, there's requirejs feature
And there's a lot more listed here: https://github.com/jrburke/requirejs/wiki/Plugins

Resources