How to invoke a javascript function (generated from typescript) trapped within “System.register()” module while using Google protobuf? - javascript

Update: It seems that, the problem is coming due to protobuf. I am fine with other solution as well, which help me to fix the Google protobuf issues. This problem boils down to:
How to integrate Google protobuf with Typescript/Javascript for the browser?
I am retaining below question for the future purpose.
We have moved our application from Javascript to Typescript for obvious advantages of OOP etc..
Earlier invoking a direct javascript function from Html was as straight forward as:
<script>window.MyFunction()</script>
Now with Typescript, all the files are combined into a single autogenerated .js file.
In this single file, individual code of every file are isolated within System.register(). It typically looks something like:
System.register("<filename>", ["<import_1>", ..., "<import_N>"],
function (exports_13, context_13) {
"use strict";
...
function MyFunction () { ... } // somewhere inside the external function
}
In short, everything written within the .ts file is wrapped in an unnamed function after running the tsc compiler.
Now, I don't know how to invoke a function, which is trapped inside another function, which is in turn listed under System.register(...)
Question: What is the correct syntax to invoke such function externally from an Html file?
<script> ??? </script>
Update:
The HTML tries to invoke in following way in the body tag:
<script>
System.import("Main").then( // Main.ts is one of the file
function (module)
{
throw 0; // Temporary, to see if we reach till here
module.main(); // "main()" is the function, which is the entry point
});
</script>
In my code, I am using "browserify" to be able to use the Google protobuf for JS. The error comes for the protobuf related files only. Those definition and source files are present in .d.ts and .js formats.
The error is something like below:
js: Uncaught (in promise) Error: Fetch error: 404 NOT FOUND
Instantiating http://localhost:50000/folder/external/Server_pb
Loading http://localhost:50000/folder/external/_External
Loading Main
Note that, 50000 is a temporary port and the "folder" is just any folder where the .js are kept. The "Server_pb" is a custom protobuf file generated.
My problem can be aptly described quite similar as this link.
Related:
What is mean by System.register in JS file?
How to call a named module from a bundle (<-- can be helpful, but don't know the syntax as a newbie)
How to start a Typescript app with SystemJS modules? (nearly duplicate, but unable to solve the problem with this approach yet)
How do I get TypeScript to bundle a 3rd party lib from node_modules? (seems like another close match; trying to dig into this right now to fix the protobuf problem)

With "google-protobuf" there are issues when used in the fashion of systemjs. It seems that Google has created it only for the nodejs. :-)
To be able to use the protobuf in Javascript for the browser, there are few things which we have to do manually. Such manual boilerplate work can be done using some scripts as well.
I am giving an iterative way, on how to achieve this:
The first step is to generate the protobuf for both JS and TS. Use following command for the same:
protoc <file1.proto> <file2.proto> ... <fileN.proto>
--proto_path=<proto_folder> \
--cpp_out=<cpp_folder> \
--js_out=import_style=commonjs,binary:<js_folder> \
--ts_out=import_style=commonjs,binary:<ts_folder>
Note that, we are using the commonjs (and not systemjs). Legends:
<proto_folder> = folder path where all these file1/2/N.proto files are stored
<cpp_folder> = folder path where you want the c++ file1/2/N.pb.cc/h files to be stored
<js_folder> = folder where you want the file1/2/N_pb.js files to be stored
<ts_folder> = folder where you want the file1/2/N_pb.d.ts files to be stored
Now in all the .d.ts (Typescript definition) files, there are certain code lines, which will give compiler errors. We need to comment these lines. Doing manually, is very cumbersome. Hence you may use sed (or ssed in Windows, gsed in Mac). For example, the lines starting with,
sed -i "s/^ static extensions/\/\/ static extensions/g" *_pb.d.ts;
same as above for static serializeBinaryToWriter
same as above for static deserializeBinaryFromReader
sed -i "s/google-protobuf/\.\/google-protobuf/g" *_pb.d.ts; // "./google-protobuf" is correct way to import
Now, while generating the *_pb.d.ts, the protoc compiler doesn't follow the packaging for Typescript. For example, if in your fileN.proto, you have mentioned package ABC.XYZ, then the fileN.pb.h will be wrapped in namespace ABC { namespace XYZ { ... } }. The same doesn't happen in the case of Typescript. So we have to manually add these in the file. However, here it won't be a simple find/replace as above. Rather, we have to find only the first occurance of any export class (which is of generated proto) and wrap the namespaces. So below is the command:
sed -i "0,/export class/{s/export class/export namespace ABC { export namespace XYZ {\\n &/}" fileN_pb.d.ts;
sed -i -e "\$a} }" fileN_pb.d.ts;
Initial importing of the google-protobuf package has to be prefixed with ./ in the case of generated _pb.js file as well
sed -i "s/google-protobuf/\.\/google-protobuf/g" *_pb.js;
Now compile the the custom Typescript files with tsc -p "<path to the tsconfig.json>", where the tsconfig.json may look like (see arrow):
{
"compileOnSave": true,
"compilerOptions": {
"removeComments": true,
"preserveConstEnums": true,
"module": "CommonJS", <=======
"outDir": "<path to generated js folder>",
},
"include": ["../*"],
"files": ["<path to file1.ts>", ..., "<path to file2.ts>"
}
Now a very important step. All the references to the the generated *_pb.d.ts files, should be referred in 1 of your custom file. That custom file may contain the wrappers around the generated classes if it's required. This will help in limiting string replacement only in that file, which is explained in the upcoming step. For example, create a custom file name as MyProtobuf.ts and import your proto as following:
import * as proto from './fileN; // from fileN.d.ts
In above step, it's important to note that the name "proto" is crucial. With that name, the .js files are auto generated. If there are several proto files in your project, then you may have to create yet 1 more file which exports all of them and then import that 1 file:
// in 'MyProtobufExports.ts' file
export * from './file1'
export * from './file2'
export * from './fileN'
import * as proto from './MyprotobufExports // in MyProtobuf.ts file
With above 2 steps, the usage of the protobuf as, var myClass = new proto.ABC.XYZ.MyClass;
Now the continuation of the important step we discussed above. When we generate the equivalent _pb.js and our custom .js files, still the special name-symbol proto will not be found somehow. Even though everything is wrapped. This is because the autogenerated JS files (from TS files), will declare a var proto. If we comment that then, that issue is gone.
sed -i "s/var proto = require/\/\/ &/g" Protobuf.js;
The final step is to put the browserify on all the .js files into a single file, as below. Due to this, there will be only single .js file, we have to deal with [good or bad]. In this command, the ordering is very important. file1_pb.js should come before file2_pb.js, if file1.proto is imported by file2.proto or vice a versa. If there is no import then the order doesn't matter. In any case the _pb.js should come before the custom .js files.
browserify --standalone file1_pb.js fileN_pb.js MyProtobuf.js myfile1.js myfileN.js -o=autogen.js
Since the code is browserified, the calling of function can be done in following way:
window.main = function (...) { ... } // entry point somewhere in the fileN.ts file
<script>main(...)</script> // in the index.html
With the above steps only, I am able to make the "google-protobuf" work within my project for the browser.

Related

How to bundle/build javascript files when one file expects a global window object

I have written a frontend animation in javascript with the logic beeing split across multiple javscript files. I would like to bundle the files together based on ES6 modules, at least the files I have written myself.
Here is the problem:
import { Webfont } from "webfontloader";
...
function animate(myText){
WebFont.load({
google: { families: ["Indie Flower"]},
fontactive: function(familyName, fvd){ //This is called once font has been rendered in browser
display(myText);
},
});
}
I import the dependent modules, however one module (webfontloader) contains the window object. This is fine when it runs in the browser but when I build and bundle it with npm und rollup.js, it throws the error:
ReferenceError: window is not defined
How can I solve this without touching the code of "webfontloader" which is an external library ?
Do I have even two options ?
option a) to bundle all my files together and leaving the external libraries external. In production, they would be included as separate script tags.
option b) to bundle all the files together into one file
Install global (https://www.npmjs.com/package/global) then use rollup-plugin-inject(https://github.com/rollup/rollup-plugin-inject) to tell rollup to replace usages of window by the one of global/window.
// rollup.config.js
import inject from 'rollup-plugin-inject'
export default {
// ...
plugins: [
inject({
include: 'node_modules/webfontloader/**',
window: 'global/window'
})
]

npm global packages: Reference content files from package

I'm in the process of building an npm package which will be installed globally. Is it possible to have non-code files installed alongside code files that can be referenced from code files?
For example, if my package includes someTextFile.txt and a module.js file (and my package.json includes "bin": {"someCommand":"./module.js"}) can I read the contents of someTextFile.txt into memory in module.js? How would I do that?
The following is an example of a module that loads the contents of a file (string) into the global scope.
core.js : the main module file (entry point of package.json)
//:Understanding: module.exports
module.exports = {
reload:(cb)=>{ console.log("[>] Magick reloading to memory"); ReadSpellBook(cb)}
}
//:Understanding: global object
//the following function is only accesible by the magick module
const ReadSpellBook=(cb)=>{
require('fs').readFile(__dirname+"/spellBook.txt","utf8",(e,theSpells)=>{
if(e){ console.log("[!] The Spell Book is MISSING!\n"); cb(e)}
else{
console.log("[*] Reading Spell Book")
//since we want to make the contents of .txt accesible :
global.SpellBook = theSpells // global.SpellBook is now shared accross all the code (global scope)
cb()//callBack
}
})
}
//·: Initialize :.
console.log("[+] Time for some Magick!")
ReadSpellBook((e)=>e?console.log(e):console.log(SpellBook))
spellBook.txt
ᚠ ᚡ ᚢ ᚣ ᚤ ᚥ ᚦ ᚧ ᚨ ᚩ ᚪ ᚫ ᚬ ᚭ ᚮ ᚯ
ᚰ ᚱ ᚲ ᚳ ᚴ ᚵ ᚶ ᚷ ᚸ ᚹ ᚺ ᚻ ᚼ ᚽ ᚾ ᚿ
ᛀ ᛁ ᛂ ᛃ ᛄ ᛅ ᛆ ᛇ ᛈ ᛉ ᛊ ᛋ ᛌ ᛍ ᛎ ᛏ
ᛐ ᛑ ᛒ ᛓ ᛔ ᛕ ᛖ ᛗ ᛘ ᛙ ᛚ ᛛ ᛜ ᛝ ᛞ ᛟ
ᛠ ᛡ ᛢ ᛣ ᛤ ᛥ ᛦ ᛧ ᛨ ᛩ ᛪ ᛫ ᛬ ᛭ ᛮ ᛯ
If you require it from another piece of code, you will see how it prints to the console and initializes by itself.
If you want to achieve a manual initalization, simply remove the 3 last lines (·: Initialize :.) and use reload() :
const magick = require("core.js")
magick.reload((error)=>{ if(error){throw error}else{
//now you know the SpellBook is loaded
console.log(SpellBook.length)
})
I have built some CLIs which were distributed privately, so I believe I can illuminate a bit here.
Let's say your global modules are installed at a directory called $PATH. When your package will be installed on any machine, it will essentially be extracted at that directory.
When you'll fire up someCommand from any terminal, the module.js will be invoked which was kept at $PATH. If you initially kept the template file in the same directory as your package, then it will be present at that location which is local to module.js.
Assuming you edit the template as a string and then want to write it locally to where the user wished / pwd, you just have to use process.cwd() to get the path to that directory. This totally depends on how you code it out.
In case you want to explicitly include the files only in the npm package, then use files attribute of package.json.
As to particularly answer "how can my code file in the npm package locate the path to the globally installed npm folder in which it is located in a way that is guaranteed to work across OSes and is future proof?", that is very very different from the template thingy you were trying to achieve. Anyway, what you're simply asking here is the global path of npm modules. As a fail safe option, use the path returned by require.main.filename within your code to keep that as a reference.
When you npm publish, it packages everything in the folder, excluding things noted in .npmignore. (If you don't have an .npmignore file, it'll dig into .gitignore. See https://docs.npmjs.com/misc/developers#keeping-files-out-of-your-package) So in short, yes, you can package the text file into your module. Installing the module (locally or globally) will get the text file into place in a way you expect.
How do you find the text file once it's installed? __dirname gives you the path of the current file ... if you ask early enough. See https://nodejs.org/docs/latest/api/globals.html#globals_dirname (If you use __dirname inside a closure, it may be the path of the enclosing function.) For the near-term of "future", this doesn't look like it'll change, and will work as expected in all conditions -- whether the module is installed locally or globally, and whether others depend on the module or it's a direct install.
So let's assume the text file is in the same directory as the currently running script:
var fs = require('fs');
var path = require('path');
var dir = __dirname;
function runIt(cb) {
var fullPath = path.combine(__dirname, 'myfile.txt');
fs.readFile(fullPath, 'utf8' , function (e,content) {
if (e) {
return cb(e);
}
// content now has the contents of the file
cb(content);
}
}
module.exports = runIt;
Sweet!

How to compile TypeScript to JavaScript with jQuery, without any additional libraries or frameworks (NPM, etc.)

I have an extremely simple single-page dashboard that does things like grab the weather, the subway alerts, etc. and I refresh it locally on my machine. It looked like:
project/
index.html
jquery-3.3.1.min.js
script.js
And I wanted to convert it to TypeScript. I ported script.js to TypeScript, but in order to use jQuery had to download the definition file from here. I now have:
project/
index.html
jquery.d.ts
script.ts
The first two lines of my TypeScript file are:
/// <reference path ="./jquery.d.ts"/>
import * as $ from "jquery"
When I run tsc *.ts, it compiles my script successfully, but the first four lines of the resulting/compiled JavaScript file are:
"use strict";
exports.__esModule = true;
/// <reference path ="./jquery.d.ts"/>
var $ = require("jquery");
And this fails in my browser with:
exports.__esModule = true; // Can't find variable: exports
I'm a pretty weak front-end developer beyond HTML/JS/jQuery, so I'm not sure where to start - I did some Googling but not much came up.
If you mean so...
use declare var $: JQuery if the type is defined
else use declare vas $: any
for the case that your editor (f.e. VSCode) supports script var declarations use
{
name: "$",
type: JQuery (or any),
ofReference: {
scriptSrc: ["*jquery.js", "*jquery.min.js", "*code.jquery*"]
}
}

“Module not found” Flow error for autogenerated file

I have three ES2015 modules: store.js, middleware-config.js and autogenerated middleware-config-settings.js with some logic and imports.
Module middleware-config-settings.js is generated from webpack.config.js when the app starts with npm start.
store.js:
// #flow
...
import middlewareConfigs from './middleware/middleware-config';
...
middleware-config.js:
// #flow
...
import defaultSettings from './middleware-config-settings';
...
Module middleware-config-settings.js is just simple JSON:
// #flow
export default {
profilingMiddlewareConfig: {
isActive: true,
},
reduxDiffStateMiddlewareConfig: {
isActive: true,
params: {
ignoredActionTypes: [],
approvedActionTypes: []
}
}
};
When I checked the project with flow I got error:
./middleware-config-settings. Required module not found
First, I tried ignore Flow check for the line:
// #flow
...
// #FlowFixMe: ignore
import defaultSettings from './middleware-config-settings';
...
That apporach works fine if middleware-config-settings.js not exists. Otherwise I got another problem: after npm start middleware-config-settings.js file created and next flow execution fails with another error:
Error suppressing comment. Unused suppression
Second, I tried add middleware-config.js into [ignore] section in .flowconfig. But then I got new error message from store.js:
./middleware/middleware-config. Required module not found
After that I add store.js into [ignore] section too and flow executes well but I think that it's not right way.
Third approach is similar to second - I just removed // #flow from middleware-config.js and flow executes fine. And that approach also not too good (all my modules should be flowed).
So I have two questions:
How can I get errorless flow execution without excluding any of that files from flow checks?
Why I got error about store.js when I add middleware-config.js into [ignore] section? If my understanding of the documentation is right so it's not correct behavior:
The [ignore] heading in a .flowconfig file tells flow to ignore files matching the specified regular expressions when type checking your code.
A few options:
Avoid autogenerating files (I assume you have already considered this and decided against it).
Create a permanent middleware-config-settings.js.flow that contains a prototypical config, and check it in to version control. Then, Flow will look at that file instead of looking for the .js file and it will typecheck, but the actual values will be generated on each run. The main downside of this approach is to make sure that the structure of your .js.flow file matches the structure of the generated .js file when you make changes.
Introduce an explicit build step that generates the required files, rather than including that step implicitly in your npm start script. Then, just make sure that you run that build step before using Flow.
Regarding your second question, I believe that if you add something to the [ignore] section, Flow just pretends it doesn't exist at all. So, if you import an ignored file from a checked file, Flow will give you an error.
I solve that problem with some hack with importing middleware-config-settings.js with require and storing module's name in the variable (for avoiding Required module not found error) and with adding #FlowFixMe (for avoiding The parameter passed to require() must be a literal string. error):
let moduleName = './middleware-config-settings';
// #FlowFixMe: import autogenerated file.
let defaultSettings = (require(moduleName): any);

Defining a TypeScript debug variable

I'm running into a brick wall with what I guess should be a simple task - I want a single variable that determines if the application is in debug state - i.e. debug = false so that I can use this variable, as you may expect, in any class and log messages accordingly. Annoyingly however, no matter what I try, I simply cannot get access to this variable.
I have an app.ts file that imports every class and instantiates them. Defining a global variable at the top of this file did not work, and likewise defining them in a global.d.ts file did not work either - the compiler simply cannot see them. For the record my tsconfig.json file looks like this:
{
"files": [
"./resources/assets/js/declarations/**/*.d.ts"
],
"compilerOptions": {
"noImplicitAny": true,
"target": "es2015"
}
}
The global.d.ts file itself is incredibly simple...
declare let appDebug: boolean;
And its value is set in the app.ts file as mentioned previously. What gives? How can I essentially pass this value into every class?
The files setting doesn't support globs/patterns - only an explicit list of files, so you should see an error when you try to compile in typescript via tsc -p . complaining about the "./resources/assets/js/declarations/**/*.d.ts" bit.
Documentation here: http://www.typescriptlang.org/docs/handbook/tsconfig-json.html

Resources