How to deal with multiple identical classes in a project? - javascript

In a multi-tenant project, tenants could have different UI and functionality. To that end, there is a structure like this:
TenantA
SiteHeader.ts (class named SiteHeader)
TenantB
SiteHeader.ts (class named SiteHeader)
... 20 other tenants
On the web page, the .js declaration dynamically generates (server-side) the reference to the correct tenant:
<script src="../{CurrentTenant}/SiteHeader.js"></script>
On this same page, Main.ts needs to reference the correct SiteHeader. Since I can't (and don't want to) have import {SiteHeader} from 'TenantA/SiteHeader' and import {SiteHeader} from 'TenantB/SiteHeader' and 20 others, I am forced to simply declare it as a var, which points to whatever js was loaded by the parent page:
declare var SiteHeader;
However, it causes Duplicate identifier errors in each SiteHeader.ts. And occasionally in the Main.ts as well. I am able to drop down to the command line and compile each SiteHeader.ts manually, but it's chore.
How can I resolve this sort of situation?
Not that it matters much, trying to bring TypeScript to an old school WebForms project in Visual Studio 2019.

You could create interface SiteHeader and make all tenants implement it. But I am not sure how runtime would work with <script> tag in combination with external modules. Have you considered internal modules (namespaces) or using dynamic import() (should be supported in TypeScript)?

Related

How do I include untyped-JavaScript, from an adjacent file, in ReasonML bindings?

While trying to get started with Reason, in one JavaScript project, I've got an extremely light file that tries to be a Reason-typed interface to the existing, heavy, library:
/* TheLibrary.re */
type engine
external addEngine : string -> engine -> unit = "" [##bs.val] [##bs.module "../"]
However, when I try to use that library in a ReasonReact project (having added #org/the-library to the bsconfig.json bs-dependencies),
/* AComponent.re */
[#bs.val] [#bs.module "#org/game-engine/dist/game-engine.js"]
external gameEngine : TheLibrary.engine = "default";
/* Further down, a React lifecycle method, */
TheLibrary.addEngine("Game", gameEngine);
I get errors about ../ being not found, relative to that React component:
./src/components/main-menu/AComponent.re
Module not found: Can't resolve '../' in '/Users/ec/Work/reason-reacty/src/components/main-menu'
I've also tried, instead of ../ in TheLibrary.re's external declaration:
#bs.module "./index.js" (the direct, ES6 entry-point for the untyped-JavaScript side of the package in question,)
#bs.module "#org/the-library", the entire name of said library (even though I'm typing inside that library???)
Please help! I'd love to be able to further adopt ML, but I'm having the hardest time wrapping my head around ReasonReact's dependency-resolution!
Additional context:
So, we're trying to build our first ReasonReact project, and we've successfully added baby's-first-opaque-types to one of our internal libraries and include that in the ReasonReact page with something like the following — which works, by the way:
/* Imports.re */
type engine;
[#bs.val] [#bs.module "#org/game-engine/dist/game-engine.js"]
external gameEngine : engine = "default";
[#bs.val] [#bs.module "#org/the-library"] [#bs.scope "default"]
external addEngine : (string, engine) => unit = "";
This yields, when we Imports.(addEngine("Game", gameEngine)), the global setup line we need: TheLibrary.addEngine("Game", GameEngine). I'm in the very first stages of trying to upstream that typing-information into the parent project, and publish that code to npm, so that all consuming projects can start to use Reason.
t sounds like you might be a bit confused about the different tools that make up your toolchain, so let's first go through them one by one to put them in their place:
ReasonReact is a library of opinionated, "thick" bindings to react.js, which despite the name isn't actually all that Reason-specific, except for its integration with Reason's JSX syntax. It would be more accurate to call it a BuckleScript library.
Reason is mostly just the syntax you use, but is often also used more broadly to refer to the ecosystem around it, and usually also imply that BuckleScript is being used.
OCaml is the underlying language. The "semantics" of Reason, if you will.
BuckleScript is the OCaml-to-JavaScript compiler. It compiles ONE source file, which is considered a module, into ONE JavaScript module, but also requires the type information of other OCaml modules as input.
Now, I suspect you already know most of that, but what you do not seem to know is that NONE of these actually do ANY dependency resolution. These next parts of your toolchain are what does that:
The BuckleScript Build System, or bsb, is what finds all the modules in your local project according to what you've specified in src and any BuckleScript libraries you've listed in bs-dependecies in bsconfig.json. It will figure out the dependency order of all these and feed them to the compiler in the correct order to produce one JavaScript module for each OCaml module (along with some other artefacts containing type information and such). But it will not resolve any JavaScript dependencies.
Lastly, webpack, or some other JavaScript bundler, is what you likely use to combine all the JavaScript modules into a single file, and which therefore needs to resolve any JavaScript dependencies. And this is likely where the error message comes from.
Using [#bs.module "some-module"] will make the BuckleScript compiler emit var ... = require('some-module') (or import ... from 'some-module' if es6 is used), but BuckleScript itself will not do anything more with it. The string you pass to #bs.module is the same string you would pass to require if it had been an ordinary CommonJS module (or whatever other module format you have configured).
Also note that the import is not emitted where the external is defined, but where it's used. You can work around, or "ground" it in a module by re-exporting it as an ordinary definition, ie. let addEngine = addEngine.
In order to precisely answer your question I would need to know which bundler you use, where you've configured BuckleScript to output its JavaScript artefacts, where the externals are used, not just defined, and where the external JavaScript module is located. But I hope all this underlying knowledge will make it easy for you and future readers to identify and resolve the problem yourself. If you're still a bit unsure, look at the compiled JavaScript artefacts and just treat them as ordinary JavaScript modules. At this point that's really all they are.

How to properly import js files in meteor

I like keeping my javascript files as small as possible and using an architectural pattern. This usually means splitting my js files in Services, Controllers, Models, Views, etc.
Meteor automatically loads all js files. However every variable defined in any js file is processed as a local variable for that file. To be able to access them I have to define them like so:
//global
my_global_function = function(){
}
//not global
var my_global_function = function(){
}
//not global
function my_global_function(){
}
Defining a variable/function without the keyword var or function is not good practice. What are the possible alternatives?
The best option is to use ES2015 modules.
Meteor does not support modules natively yet, but there are packages that bring this support.
For example, universe:modules.
With modules you can import and export some variables/functions/classes/etc:
// module1.import.js
import alertSomething from './module2'
Meteor.startup(() => {
alertSomething();
});
// module2.import.js
export default function alertSomething() {
alert('something');
}
universe:modules is not the only solution, there are other similar projects. I love this one specially https://github.com/thereactivestack/kickstart-simple. It replaces Meteor's build system with WebPack's and enables hot-reloading if you use React.
UPDATE:
Meteor does support ES6 modules now
Since you seem very interested in proper architectural design, I would recommend looking at Meteor packages. Essentially, you have to declare any globally exposed variable in the package.js configuration, which is what you want: as little "leakage" as possible. Within a package, you can afford to be a little bit more sloppy, but you can still use var (and the absence of var) for more fine-grained control within a package. This can be done within Meteor right now.
You can find most information in the package documentation. The easiest way to get started is to create a package using meteor create --package [package-name]. This sets up a basic structure to play with. The api.export function is what controls exposed variables. See doc here.
Additionally, be careful with adding an unnecessary layer on top of the intrinsic Meteor architectural design. Templates are views, server side methods are services, etc. There are only some things that you don't get out of the box, so usually you'll add something like Astronomy or SimpleSchema.
Adding too much of your own architecture is probably going to end with you fighting the Meteor framework itself...

How can I access constants in the lib/constants.js file in Meteor?

I followed the documentation to put the constants in the lib/constants.js file.
Question:
How to access these constants in my client side html and js files?
Variables in Meteor are file-scoped.
Normally a var myVar would go in the global Node context, however in Meteor it stays enclosed in the file (which makes it really useful to write more transparent code). What happens is that Meteor will wrap all files in an IIFE, scoping the variables in that function and thus effectively in the file.
To define a global variable, simply remove the var/let/const keyword and Meteor will take care to export it. You have to create functions through the same mechanism (myFunc = function myFunc() {} or myFunc = () => {}). This export will either be client-side if the code is in the client directory, or server-side if it is in the server directory, or both if it is in some other not-so-special directories.
Don't forget to follow these rules:
HTML template files are always loaded before everything else
Files beginning with main. are loaded last
Files inside any lib/ directory are loaded next
Files with deeper paths are loaded next
Files are then loaded in alphabetical order of the entire path
Now you may run into an issue server-side if you try to access this global variable immediately, but Meteor hasn't yet instantiated it because it hasn't run over the file defining the variable. So you have to fight with files and folder names, or maybe try to trick Meteor.startup() (good luck with that). This means less readable, fragile location-dependant code. One of your colleague moves a file and your application breaks.
Or maybe you just don't want to have to go back to the documentation each time you add a file to run a five-step process to know where to place this file and how to name it.
There is two solutions to this problem as of Meteor 1.3:
1. ES6 modules
Meteor 1.3 (currently in beta) allows you to use modules in your application by using the modules package (meteor add modules or api.use('modules')).
Modules go a long way, here is a simple example taken directly from the link above:
File: a.js (loaded first with traditional load order rules):
import {bThing} from './b.js';
// bThing is now usable here
File: b.js (loaded second with traditional load order rules):
export const bThing = 'my constant';
Meteor 1.3 will take care of loading the b.js file before a.js since it's been explicitly told so.
2. Packages
The last option to declare global variables is to create a package.
meteor create --package global_constants
Each variable declared without the var keyword is exported to the whole package. It means that you can create your variables in their own files, finely grain the load order with api.addFiles, control if they should go to the client, the server, or both. It also allows you to api.use these variables in other packages.
This means clear, reusable code. Do you want to add a constant? Either do it in one of the already created file or create one and api.addFiles it.
You can read more about package management in the doc.
Here's a quote from "Structuring your application":
This [using packages] is the ultimate in code separation, modularity, and reusability. If you put the code for each feature in a separate package, the code for one feature won't be able to access the code for the other feature except through exports, making every dependency explicit. This also allows for the easiest independent testing of features. You can also publish the packages and use them in multiple apps with meteor add.
It's amazing to combine the two approaches with Meteor 1.3. Modules are way easier and lighter to write than packages since using them is one export line and as many imports as needed rather than the whole package creation procedure, but not as dumb-error-proof (forgot to write the import line at top of file) as packages.
A good bet would be to use modules first, then switch to a package as soon as they're tiring to write or if an error happened because of it (miswritten the import, ...).
Just make sure to avoid relying on traditional load order if you're doing anything bigger than a POC.
You will need to make them global variables in order for other files to see them.
JavaScript
/lib/constants.js
THE_ANSWER = 42; // note the lack of var
/client/some-other-file.js
console.log(THE_ANSWER);
CoffeeScript
/lib/constants.coffee
#THE_ANSWER = 42
/client/some-other-file.coffee
console.log THE_ANSWER

Best way to import JavaScript files into one file?

I have a background in coding in languages that have a concept of "classes". Now that I am coding JavaScript, I would like to code in a similar way so that each object oriented "class" I create is its own separate file.
see Accessing "Public" methods from "Private" methods in javascript class
see http://phrogz.net/JS/classes/OOPinJS.html
In other languages, I would create import statements at the top of the class file to ensure other custom classes that were used within a class file so that the other custom classes were compiled into the final binary.
Of course JavaScript is not a compiled language; however, I would still like to be able to be include some kind of "import" statement at the top of custom class files so I could ensure the imported JS "class" file was available for the user's browser to download.
It would be ideal if there were a 3rd party tool that combined all of my separate class files into one JS file so the browser only had to make one HTTP request for a single JS file instead of many calls for each indicidual JS "class". Does anyone know if such a tool exists where it would do the following:
allowed me to choose which JS files that I wanted to include in a single JS file
crawled thru the files I selected in step 1 and found all the "import" statements at the top of each custom "class" file. These "import" statements could simply be specially formatted comments in the code that the 3rd party recognizes as import statements.
The 3rd party would then create the single JS file with all of the files that were selected from step 1 and from all of the imported files that were found in step 2.
Some popular JavaScript frameworks seem to do just that. For example, jQueryUI allows you to customize the download of a single jQueryUI source file by allowing the user to check off which objects you want to use. If you uncheck an element that is needed for an item that you checked off, then the form tells you that there is a dependency you need to rectify before being able to proceed to download the file.
see http://jqueryui.com/download/
So is there a 3rd party tool that allows a developer to use some kind of "import" statement comment to ensure that many dependent JS files (and only the ones that the developer needs) to be combined into a single JS file?
RequireJS was built for exactly this purpose.
Have a look at Require.js. It lets you import various javascript files in a modularized fashion and add the required dependencies between them. Also at the end you can minify them all into one single JS file using r.js
A trivial batch file can do this for you:
#for %i in (classes/*.js) type %i >> build.js
This works best if your JS source files are all in one folder, and this example assumes that folder is named classes. It gets a bit more complicated if you have subfolders, but a similar principle can be applied.
Have a look at GruntJS, JQuery uses it for building. If you don't care for HTTP requests, you can use already mentioned RequireJS, which also has nice async methods to load files, which can improve perfomance in some situations.
Check out this class https://www.youtube.com/watch?v=KnQfGXrRoPM
This allows for importing on the fly within classes. also it allows
for importing all classes within an folder and all of its sub folders.
and its really simple because it is just a prototype function added to String.
just by adding the importer class you will call in classes like "com.project.Classfile.js".import();
or "com.project.*".import() to get all sub-classes.
fork on - https://github.com/jleelove/Utils

What is a typescript AMD module and do I need it for an ASP MVC4 web application?

I started to use Web Essentials and I see there's an option "Use the AMD module". I am using typescript for an ASP MVC4 application. Can someone explain what the AMD module is all
about. Is this something that I should know about?
AMD is one way to format and load modular JavaScript. See here: http://addyosmani.com/writing-modular-js/ and especially here: http://requirejs.org/docs/whyamd.html
To quote from that latter source:
The AMD format comes from wanting a module format that was better than
today's "write a bunch of script tags with implicit dependencies that
you have to manually order" and something that was easy to use
directly in the browser.
Essentially AMD allows you to load JavaScript modules on demand, and provides a format for encapsulating their content so the Global namespace isn't polluted.
In TypeScript, with the AMD compiler switch that you've discovered set to 'on', you export a module like this:
export module pe.components {
export class Component { // 'export' makes this visible outside the module
}
class FriendComponent { // no 'export' so this is only visible inside the module.
}
}
And in another file you import this module like this:
import c = module('relative-path-to-file/pe.components');
And then use it like this:
var component:Component = new c.pe.components.Component(); // Works, because Component is exported
... but not this:
var friend:FriendComponent = new c.pe.components.FriendComponent(); // Shouldn't work* because FriendComponent is not exported.
(* there was a bug that made non-exported interfaces visible outside their declaring modules. I think this has been fixed in TS 0.8.1).
As to the second part of your question - this is really too broad. If your architecture requires you to load new functionality (plug-ins or applets within a single page application, for example), then yes, AMD modules and a loading framework such as RequireJS may be the way to go. If, on the other hand, you know in advance all the functionality your users are going to require, you might be better off just minifying your scripts thoroughly and loading them in advance as a single file.
I don't think the fact that you are working with MVC is relevant here: the question is whether your client-side architecture warrants an asynchronous, modular approach.

Categories