Webpack: single scope for multiple js files? - javascript

Imagine two javascript files and one entry point file:
app.js:
require(a.js);
require(b.js);
a.js:
var a = 4;
b.js:
var b = a+1;
console.debug(b);
This unfortunately does not work because the context of file a is lost in file b, meaning b.js does not know of any variable called a.
How can I fix that using Webpack - I simply want get the same result as
<script src="a.js"></script>
<script src="b.js"></script>
with the added effect of bundling through Webpack.

Using ES2015 modules (which may not be available for you, you can use require instead)
a.js:
export var a = 4;
b.js
import { a } from "./b.js";
var b = a+1;
console.debug(b);
Webpack is a module building/bundling system that works by creating UMD (universal modules) from javascript files. You have to import/export these modules in order for them to be in scope.

Related

How to include/import an entire JS file with Webpack

I am new to Webpack, but I have a habit of building my web apps in the following manner:
Declare a global variable for the project.
Create several JS files that use the global variable to declare functions and variables
Link these separate js files in the HTML file, and combine them when going to production.
For example my first JS file would be "init.js":
let FP = {}; // Global Object
My second would be "processing.js":
FP.addNumbers = function(a, b){
return a+b;
}
The HTML would include:
<script src="init.js"></script>
<script src="processing.js"></script>
Then in production I use a node plugin I wrote to parse the HTML page and combine all the JS files into one.
I want to do something similar with Webpack, but the only way I found to combine files is with "include". Which requires each file use a separate variable name.
I literally just want to dump the contents of my separate JS files into init.js. Is there a way to do this?
If you're going to use Webpack to bundle your code, the right approach to something like this would be to take advantage of modular imports and exports to coordinate data between modules, otherwise the use of Webpack isn't really doing anything for you.
For example, here, try the following:
// index.js
import { addFns } from './addFns.js';
const FP = {};
window.FP = FP; // use this if the object needs to be global
addFns(FP);
// addFns.js
export const addFns = (FP) => {
FP.addNumbers = function(a, b){
return a+b;
}
};
Then, Webpack can create the complete bundle with only a single .js file as output automatically, and it'll be ready for production with any more post-processing.

Webpack import window module to a namespace

I am using 2 modules, that are using "window.ModuleName" to export themself. Both of them use the same ModuleName.
window.z = a;
window.z = b
The first module a is standalone module, as I am importing it through $.getScript, when it is needed.
The second module b is bundled through webpack.
Module a window.z overwrite module b window.z when is loaded. Therefore, I would like to change the scope of the module b by assigning it to a variable or a namespace with webpack instead of window scope through webpack or any other possibility.
newScope.z = a
Both modules are not defined by me, therefore, I cannot change the export.
Is there any way to do that in professionally?
create a startup.js file (to be included/required before you need a or b) where you do this:
require('a')
var z1 = window.z
window.z1 = z1
require('b')
now window.z1 is the a.z, and window.z is b.z, yo have them both
I could only solve that through using string replace loader from webpack and replace the export of module b from window.z => window.zb.
Unfortunately it seems there is no possibility to change the scope of a variable from window global object to local one.

How should I define a global TypeScript variable in a definition file so that it can be imported?

I have an external JS library with a global parameter:
function Thing() { ... }
...
var thing = new Thing();
There is a TypeScript definition file, so in thing.d.ts:
declare var thing: ThingStatic;
export default thing;
export interface ThingStatic {
functionOnThing(): ThingFoo;
}
export interface ThingFoo {
... and so on
Then I import this into my own TS files with:
import thing from 'thing';
import {ThingFoo} from 'thing';
...
const x:ThingFoo = thing.functionOnThing();
The problem is that transpiles to:
const thing_1 = require("thing");
...
thing_1.default.functionOnThing();
Which throws an error. I've asked about that in another question, and the suggestion is to use:
import * as thing from 'thing';
That doesn't fix it - it gives me thing.default in TS but then that's undefined once transpiled to JS.
I think there's something wrong with thing.d.ts - there must be a way to define a typed global parameter that can be imported.
How should I write thing.d.ts so that it represents the JS correctly and doesn't transpile to include default or other properties not actually present?
If the only way to use that library is by accessing its globals (as opposed to importing it as node module or amd or umd module), then the easiest way to go is have a declaration file without any exports at top level. Just declaring a variable is enough. To use it, you have to include that declaration file when compiling your typescript code, either by adding it to files or include in tsconfig.json, or directly on command line. You also have to include the library with a <script> tag at runtime.
Example: thing.d.ts
declare var thing: ThingStatic;
declare interface ThingStatic {
functionOnThing(): ThingFoo;
}
declare interface ThingFoo {
}
test-thing.ts
const x:ThingFoo = thing.functionOnThing();
can be compiled together
./node_modules/.bin/tsc test-thing.ts thing.d.ts
the result in test-thing.js:
var x = thing.functionOnThing();
See also this question about ambient declarations.
Note: there are module loaders out there that allow using global libraries as if they were modules, so it's possible to use import statement instead of <script> tag, but how to configure these module loaders to do that is another, more complicated question.

Best way to make accessible api with es6 modules

I'm writing a browser api with es6 (translated with babel). Since other js are going to call my api, I need to make my api accessible from the global (window) scope.
With module pattern in plain js (es5) I would have done something like this:
myApp.js
var myApp = (function(){
var a, b, c;
function setA(){
// set a
}
// other functions
return {
"setA": setA,
// ... other functions
};
}());
myAppExt.js
window.myApp = window.myApp || {};
(function(app){
app.Module1 = (function(){
return {
// methods of this module
};
}());
}(myApp));
With es6 we're not supposed to do something like this but to achieve the same objective I'm writing my app in this way:
myApp.js
import method1 from './common/module1.js'
import init from './folder/init.js'
import {method2, method3} from './folder/module2.js'
import log from './common/log.js'
const myApp = {};
window.myApp = myApp;
myApp.method1 = method1;
myApp.method2 = method2;
myApp.method3 = method3;
myApp.log = log;
init();
Is this the best way to achieve this goal or is there any better design solution?
If you are going to develop a library you will probably end up generating one single bundled file which contains all the contents of your library. To create a a a bundle you need a tool like webpack or browserify, both tools allow you to create your library in a way that can be consumed in many ways (AMD, CommonJS, global...).
So you need to create a root module:
myLibrary.js
import something from './framework/module1.js';
import somethingElse from './framework/module2.js';
// export public parts of your library
export {something};
export {somethingElse };
Then use webpack library setting:
{
output: {
// export itself to a global var
libraryTarget: "var",
// name of the global var: "Foo"
library: "Foo"
},
externals: {
// require("jquery") is external and available
// on the global var jQuery
"jquery": "jQuery"
}
}
More info here.
You can also use browserify standalone setting:
--standalone -s Generate a UMD bundle for the supplied export name.
This bundle works with other module systems and sets the name
given as a window global if no module system is found.
More info here.
I've actually merged the solution proposed from OweR ReLoaDeD with another I've found.
After configuring webpack to export global variable, instead of importing and then exporting methods I've exported directly what I needed to be available in the public api.
export {method} from './common/file1.js';
export * from './dir/file2.js'
export {anothermethod} from './common/file2.js
Thank you for the help

Typescript internal modules across multiple files vs js minification

I'm working on a large typescript project. I prefer to create many small classes and put each in a separate file, and use deeply nested namespaces ('internal modules' in typescript). I consider this to be good practice as it encourages encapsulation, testability, reusability etc. I have more than a hundred small typescript files with deep namespaces.
The compiled .js output for each typescript file contains 3 lines of autogenerated 'boilerplate' for each file, for each level of namespace (2 at the top, one at the bottom). For example, here is a typescript file containing a single empty class, inside 4 levels of namespace:
module a.b.c.d {
export class MyClass {
constructor() {
}
}
}
This compiles into the following .js:
var a;
(function (a) {
var b;
(function (b) {
var c;
(function (c) {
var d;
(function (d) {
var MyClass = (function () {
function MyClass() {
}
return MyClass;
})();
d.MyClass = MyClass;
})(d = c.d || (c.d = {}));
})(c = b.c || (b.c = {}));
})(b = a.b || (a.b = {}));
})(a || (a = {}));
For performance, I want to merge/minify my .js files into a single file for production. If I simply append the files in the correct order, the boilerplate for getting in and out of the namespace will be repeated for each file. In the case of small .ts files, this will comprise a significant overhead.
My question: is there some way to merge/minify my .js files in a way that strips out the boilerplate for these deep namespaces in cases where sequential .js files share the same namespace?
I understand that migrating from internal to external modules can be a lot of work but it is your best option for the future.
Once you have created external modules you can compile your TypeScript code into CommonJS or AMDmodules using the --module flag:
tsc myfile.ts --module "amd"
tsc myfile.ts --module "commonjs"
Then you can use the require.js optimizer to bundle all the AMD modules in your application into one single optimized file.
If you use commonJS you can use Browserify to bundle all your external modules into one single optimized file. Hope it helps.
I have created a Github project that showcases how to automate this process https://github.com/remojansen/modern-workflow-demo

Categories