Preserve order of modules in Webpack - javascript

import('./A');
import('./B');
import('./C');
export class Person {};
A, B and C are plain JS (es5) libraries which use global window object and depends on each other.
Looking at the output file, I see that Webpack (awesome-typescript-loader) changes the order of modules, and it causes issues.
How to include them in the output file in exact same order?

The right answer is probably to convert your files to typescript but this answer assumes you have time to completely convert your existing js files to ts. If for some reason you don't, they should still work pretty well as partially converted ts files you just might have to take some conversion shortcuts.
I would only recommend doing this with existing known good working js files and only if you can't take the time to convert them which is why I am assuming you are asking the question.
copy your javascript *.js file content into new typescript *.ts files.
resolve all the lint complaints (you can err on the side of flexibility for your JS code)
i.e. using
// #ts-ignore
and the "any" type only when necessary
export the things used by your other files in your new ts files
i.e.
export {varible}
import those things by the dependent files i.e.
import {varible} from "./variableFile"
this should resolve your import order issues.

Related

How to use d.ts file for intellisense in VS Code similar to NPM approach

I have a nodejs project (in JS). Unfortunately, I have to utilize a lot of node global variables.
Everything works fine (even a lot of people are suggesting not to use globals) except thing:
There is no intellisense for globals. So every time I want to use, let's say, global function/object I need to look in its code and figure out what are the
parameters, what does it return, etc.
Let's say I have a global variable which is a pure object:
foo = {
bar: {
level2: {
level3: {
level4: "abc
}
}
}
}
It's quite annoying to deal with it since I can't "see" the structure of the object when using it and it's easy to make a mistake when writing code.
The reason why I posted this question is the ...npm packages
There are plenty of packages written in vanilla JS and most of them are utilizing the power d.ts files.
Once you install the package you can use it from any place in your projects and VS code will have intellisense for them. If you will click on tooltip (IDK how it's called... Type definition tooltip?) of VS code
you will be navigated to the d.ts file of the package (not the actual implementation of the command).
So my question is how to do the same in my project. I'm not going to publish it as npm I just want a d.ts file somewhere in the project so I can
use my global without looking into its implementation every time I need to recall what it does.
Let inside your .d.ts file be anything
To access variables, functions, interface add this line in your .ts file, VS code IntelliSense will suggest you
/// <reference path="./test.d.ts" />
If you want to use this test.d.ts all over your project not just on any particular file. Then add this line in tsconfig.json
"files" : [ "./src/test.d.ts" ]
Update as mentioned in the comment section
my js file which I am assuming similar to what you are trying to do
export const testString = 'aditya';
in your js file you
/// <reference path="test.js" />
Regardless of if it is possible or not, the worst drawback of what you are looking for is that the declaration file(s) must be kept updated by hand each time a global variable/function is changed.
There are plenty of packages written in vanilla JS and most of them are utilizing the power d.ts files.
Usually .d.ts files are not written by hand, but are produced by tsc: many of the packages you are speaking about are probably written in TypeScript and distributed as JavaScript packages (to be used in JavaScript projects as well) with an associated index.d.ts file (to be used in TypeScript projects)
even a lot of people are suggesting not to use globals
+1

Use all files in a folder like a one big JS

Is it possible to configure ESLint in WebStorm so functions, variables, etc. are parsed also from files in the same folder? In my build process, I concatenate all files in the same folders into big closures, for example:
src/
main/ ===> "main.js"
api.js
init.js
ui.js
constants.js
.
.
renderer/ ===> "renderer.js"
core.js
events.js
I would like ESLint to treat all those files just like one, so I don't get "undef" errors for things that are defined.
If it can't be done automatically, I wouldn't mind to create a manual configuration specifying all those files if that is possible.
EDIT: Why I don't (can't) use modules? TLDR- legacy code and project requirements.
I need to minify all code. Current closure compiler can transpile ES6 into ES5, but I found some ES6 features very prone to produce broken code. So I am forced to use ES5.
As I need ES5. I would only be able to use require() to use modules. Now that's a problem, as require() is a dynamic include and it impacts performance on my context (big electron app for modest power devices)
So to answer #Avin_Kavish, I agree what I do is "technically non conforming", but at the end of the build process it is, because each folder has been grouped into a file. That file is the module or the script. To group the files I use a Gradle plugin https://github.com/eriwen/gradle-js-plugin, I inject a "closure header" and a "closure footer", and all the files in between in the order I want.
Despite the inconvenience, at the end I get super-compact nodeJS code, with all methods obfuscated, etc.
I ended up using #Patrick suggestion, thanks for that!
EDIT 2
WebPack + Electron-WebPack turned out to be what I was looking for.
BTW- I think the proper way to do this is if EsLint would allow a "folder" sourceType.
You didn't provide code examples in your question, but I assume you do something like this:
api.js
const api = {
fetchData() {
// some code that fetches data
}
};
core.js
const core = {
init() {
api.fetchData();
}
};
The ESLint rule that causes errors when you lint these JavaScript modules is the no-undef rule.
It checks for variables that are used without having been defined. In the code example core.js above, this would be api, because that is defined in another module, which ESLint doesn't know about.
You don't care about these errors, because in your actual JS bundle used in production, the code from api.js and core.js is concatenated in one bundle, so api will be defined.
So actually api in this example is a global variable.
The no-undef rule allows you to define global variables so that they won't cause errors.
There are two ways to do this:
Using Comments
At the beginning of your core.js module, add this line:
/* global api */
Using the ESLint Config
As explained here – add this to your .eslintrc file:
{
"globals": {
"api": "writable"
}
}
Side Note
As some commenters to your question pointed out, it would probably be better to use import and export statements in the modules, together with a module bundling tool like webpack to create one bundle from your JavaScript modules.
A physical JavaScript file with an import/export statement is a module by the standard. A single .js file without import/export is a script by the standard. What you are trying to do is non-conforming to this, there is no specification in ECMAScript that allows splitting a single script or module across several files. I do get where you are coming from, for example: C# has partial classes that allows you to split a class across multiple files. But trying to replicate this without a standard syntax is not wise. Especially, when import/export can and will do the job for you
For example, with the following assumptions, your main.js can be refactored to,
constants.js // <--- constants
ui.js // <--- logic to build UI
api.js // <--- exposing public api
init.js // <--- setup code before use
// main.js
// If you name this index.js you can import it as 'src/main' instead of 'src/main/main.js'
import { A,B } from './constants'
import { api } from './api'
import { displayUi } from './ui'
import { init } from './init'
init(A);
displayUi(B);
export { api } // <-- re-expose public api

How to make TypeScript output valid ES6 module import statements?

All major browsers have supported ES6 modules for some time.
These differ from many of the server-side approaches in that they need to specify the exact file to import from - they can't use file discovery.
This makes sense - in Node applications or bundlers like WebPack they only really need the name of the module, and then can spend a bit of extra time discovering the specific file that holds the code. On the web that could be a lot of wasted round trips (is 'library' in library/index.js, or library/library.js, or library.js? require() doesn't care but on the web we have to).
TypeScript has ES6 modules support (set "module": "es6" in tsconfig.json) but it appears to be using a file discovery approach...
Suppose I have library.ts:
export function myFunction(...) { ... }
Then in app.ts:
import {myFunction} from './library';
var x = myFunction(...);
However, this is unchanged when transpiles - the TS output still has the 'library' name for file discovery, which doesn't work. This throws an error because 'library' isn't found:
<script type="module" src="app.js"></script>
In order for ES6 modules to work the TS output needs to reference the specific file:
import {myFunction} from './library.js';
var x = myFunction(...);
How do I make TS output valid ES6 module import statements?
Note: I am not asking how to make a bundler join the TS output into a single file. I specifically want to load these files individually using <script type="module">
This is a bug in TypeScript, though there's some debate about whether it should be fixed.
There is a workaround: while TS won't allow you to specify a .ts file as the source of a module, it will let you specify a .js extension (and then ignore it).
So in app.ts:
import {myFunction} from './library.js';
var x = myFunction(...);
This then outputs correctly in app.js, and TS has found the import definitions and bindings correctly.
This has one advantage/gotcha to be aware/careful of: TS just ignores the .js extension and loads the rest of the path with the usual file discovery. This means that it will import library.ts, but it would also find definition files like library.d.ts or import files in a library/ folder.
That last case might be desirable if you're joining those files together into a library.js output, but to do that you're going to be looking at either lots of nested tsconfig.json files (messy) or possibly the pre-transpiled output of another library.
The compiler takes a module kind flag:
--module ES2015
And you'll also need to be targeting ECMAScript 6 / 2015...
--target ES2015
You need both the module kind and the compilation target to be ECMAScript 2015 minimum to have "zero transformation imports".
Your import statements should look half-way between your two examples:
import {myFunction} from './library';
Additional Notes
There is still clearly a lot of discussion about module resolution... there is the TC39 specification, and the WHATWG specification - plus Node is currently still file-extention-less... looks like RequireJS might live longer than we all thought... please see:
The TypeScript thread for supporting file extensions during import transpilation (i.e. will it add the file extension?).
Recommendation
Stick with a module loader, for example RequireJS or SystemJS. This also means your modules can be shared between browser and server by using UMD or System module kinds repectively.
Obviously, once the ECMAScript discussion reaches a conclusion this will need a revisit.
For a personal project I went the other way. Since I had NPM calling a shell script to copy index.html over to the /build folder, I had the shell script then mass-rename all .js files to have no extension at all.
I did have to inform IIS in "MIME Types" section that an extension-less file should have MIME type application/javascript for that particular site, but it did indeed work. No webpack, no SystemJS, nothing. Index.html just had a hard-coded
<script type="module">
import "./app";
</script>
This was important because I was using a testing framework jest which did not like me putting the .js into the typescript import statements.

How to use js modules from non-module files

I'm a beginner at using js modules.
I'm working on a fairly simple web application. It uses typescript and angular 2, which heavily relies on modules.
Most of my app ts files 'import' one or many js modules (usually mostly angular 2 modules).
As I understand, because my app ts files have a top level 'import', they are automatically considered a js module by typescript.
However, I want any of my app ts files to be accessible by any other of my app ts files, without having to 'import' each other. But because they are now modules themselves, ts requires me to do that...
Is it possible?
It seems crazy to me that for each of my app ts file, I should have to declare every other of my app ts files that are used in there (I like to have tiny files with a single class/interface). In addition, this relies on relative paths which breaks as soon as I restructure my folder structure.
Am I thinking about this the wrong way?
You must have a js file which is an entry point to your application right?.. So in that file just import all the modules which you want to access without importing and attach them to the window object. Since the window object is available globally, you can access your module from anywhere without importing the corresponding module. For example,
Consider this scenario:
You have a module in a file called module1.ts
The entry point of your application is a file called index.ts
And you have a module2 where you require something from module1
// module1.ts
function add(first: number, second: number): number {
return first + second
}
export {add}
in your index.ts
// index.ts
import {add} from '<path to module1>/module1';
window.add = add
Now in your module2
// module2.ts
window.add(1, 2)
Since the window object is available globally you can attach as many properties to it as you like.
As far as the type resolution is concerned you can declare a window module with the add function you require in a .d.ts file as follows:
declare module window {
add: (first: number, second: number) => number
}
Declaring dependencies (e.g modules) for each file is a double-edged sword.
The advantage is that there is no 'magic' - you know exactly where each function, variable, class etc. is coming from. This makes it much easier to know what libraries / frameworks are being used and where to look to troubleshoot issues. Compare it to opposite approach that Ruby on Rails uses with Ruby Gems, where nothing is declared and everything is auto-loaded. From personal experience I know it becomes an absolute pain to try to workout where some_random_method is coming from and also what methods / classes I have access to.
You're right that the disadvantage is that it can become quite verbose with multiple imports and moving relative files. Modern editors and IDEs like WebStorm and Visual Studio Code have tools to automatically update the relative paths when you move a file and also automatically add the imports when you reference code in another module.
One practical solution for multiple imports is to make your own 'group' import file. Say you have a whole bunch of utility functions that you use in all your files - you can import them all into a single file and then just reference that file everywhere else:
//File: helpers/string-helpers.ts
import {toUppercase} from "./uppercase-helper";
import {truncate} from "./truncate-helper";
export const toUppercase = toUppercase;
export const truncate = truncate;
Then in any other file:
import * as StringHelpers from "../path-to/helpers/string-helpers";
...
let shoutingMessage = StringHelpers.toUppercase(message);
The disadvantage of this is that it may break tree shaking, where tools such as webpack remove unused code.
Is it possible
Not in any easy way. The ts file is a module and uses e.g. module.exports (if commonjs) that will need to be shimmed out. And that is just the runtime story. The TypeScript story will be harder and one way would be to make a .d.ts file for the module stating the contents as global.
Like I said. Not worth doing. Modules are the way forward instead of making something hacky.
It's not crazy at all. You are definitively thinking in the wrong way.
Actually what you don't like it's a common feature in all modern programming languages and it makes the code and structure of the app a lot clearer and simple to understand.
Without imports and going to old school way looks very crazy to me :)
You can have only chaos with so many global variables.

How to introduce typescript on a big nodeJS code basis step by step?

I do own quite a big code basis implemented in JavaScript based on NodeJS. I do want to give typescript a try and want to implement all new modules in typescript. Just to see how it performs and how I like the idea. I do need to be able to revert back at any time so I do not want to put to much effort into the migration.
I started by using WebStorm and Gulp to support typescript. I created a new module and used the import / require combination.
import settings = require("./settings");
import _ = require("lodash-node");
By doing so I receive a couple of errors.
Error:(22, 27) TS2307: Cannot find external module './settings'.
Error:(23, 20) TS2307: Cannot find external module 'lodash-node'.
Those modules have no typescript definition file and are plain javascript modules (lodash is obviously a external library). I do have a couple of dependencies and I do not want to create definitions manually even if they are just empty stubs. As mentioned I do want to keep the integration as simple as possible.
Is there a general flag or something like that I can set?
The easiest way to proceed (if you don't want type information for a module) is to write:
declare module "lodash-node" {
var notTyped: any; // naming is unimportant here
export = notTyped;
}
Put that in a separate .d.ts file and /// <reference> it. Then _ will be of type any in your implementation file and the compiler won't complain about not know what module you're talking about.
You can use webpack's typescript-loader and start from there. Make sure you put target: 'node' on your webpack config so you don't have all the browser junk.

Categories