I have recently rewritten a bunch of old JS to ES2015 making use of module import/exports. I'm using Rollup and Babel to transpile this back.
The libraries are integrated into a number of other sites I don't have control of so I need to be cautious with code to make sure I don't pollute global, doesn't throw errors, etc.
gulpfile.js
var rollupBabel = rollupPluginBabel({
babelrc: false,
presets: [
"babel-preset-es2015-rollup"
]
});
merged.add(rollup({
entry: './js/bnr.js',
format: "es",
plugins: [
rollupBabel
]
})
.pipe(source('bnr.js'))
.pipe(gulp.dest('./compiled/js/')));
bnr.js
import * as helpers from "../lib/helpers";
import moment from "../../node_modules/moment/src/moment";
class Connect {
constructor(window, document) {
this.init();
}
init()
{
// Stuff happens here
}
}
Output
// Helpers and what not here
var hookCallback;
function hooks() {
return hookCallback.apply(null, arguments);
}
// This is done to register the method called with moment()
// without creating circular dependencies.
function setHookCallback(callback) {
hookCallback = callback;
}
function isArray(input) {
return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]';
}
// The rest of moment.js
As you can see all the moment.js related code is being output without an closure/wrapper to keep it out of global. As a result I'm getting various errors on consuming sites.
How can I import moment.js or reconfigure the gulp task to import moment without polluting the global namespace?
Thanks
As suggested by #Bergi the format was the issue, switching to iife wraps the whole thing in a closure to solve the problem.
Related
Is it possible to pass options to ES6 imports?
How do you translate this:
var x = require('module')(someoptions);
to ES6?
There is no way to do this with a single import statement, it does not allow for invocations.
So you wouldn't call it directly, but you can basically do just the same what commonjs does with default exports:
// module.js
export default function(options) {
return {
// actual module
}
}
// main.js
import m from 'module';
var x = m(someoptions);
Alternatively, if you use a module loader that supports monadic promises, you might be able to do something like
System.import('module').ap(someoptions).then(function(x) {
…
});
With the new import operator it might become
const promise = import('module').then(m => m(someoptions));
or
const x = (await import('module'))(someoptions)
however you probably don't want a dynamic import but a static one.
Concept
Here's my solution using ES6
Very much inline with #Bergi's response, this is the "template" I use when creating imports that need parameters passed for class declarations. This is used on an isomorphic framework I'm writing, so will work with a transpiler in the browser and in node.js (I use Babel with Webpack):
./MyClass.js
export default (Param1, Param2) => class MyClass {
constructor(){
console.log( Param1 );
}
}
./main.js
import MyClassFactory from './MyClass.js';
let MyClass = MyClassFactory('foo', 'bar');
let myInstance = new MyClass();
The above will output foo in a console
EDIT
Real World Example
For a real world example, I'm using this to pass in a namespace for accessing other classes and instances within a framework. Because we're simply creating a function and passing the object in as an argument, we can use it with our class declaration likeso:
export default (UIFramework) => class MyView extends UIFramework.Type.View {
getModels() {
// ...
UIFramework.Models.getModelsForView( this._models );
// ...
}
}
The importation is a bit more complicated and automagical in my case given that it's an entire framework, but essentially this is what is happening:
// ...
getView( viewName ){
//...
const ViewFactory = require(viewFileLoc);
const View = ViewFactory(this);
return new View();
}
// ...
I hope this helps!
Building on #Bergi's answer to use the debug module using es6 would be the following
// original
var debug = require('debug')('http');
// ES6
import * as Debug from 'debug';
const debug = Debug('http');
// Use in your code as normal
debug('Hello World!');
I've landed on this thread looking up for somewhat similar and would like to propose a sort of solution, at least for some cases (but see Remark below).
Use case
I have a module, that is running some instantiation logic immediately upon loading. I do not like to call this init logic outside the module (which is the same as call new SomeClass(p1, p2) or new ((p1, p2) => class SomeClass { ... p1 ... p2 ... }) and alike).
I do like that this init logic will run once, kind of a singular instantiation flow, but once per some specific parametrized context.
Example
service.js has at its very basic scope:
let context = null; // meanwhile i'm just leaving this as is
console.log('initialized in context ' + (context ? context : 'root'));
Module A does:
import * as S from 'service.js'; // console has now "initialized in context root"
Module B does:
import * as S from 'service.js'; // console stays unchanged! module's script runs only once
So far so good: service is available for both modules but was initialized only once.
Problem
How to make it run as another instance and init itself once again in another context, say in Module C?
Solution?
This is what I'm thinking about: use query parameters. In the service we'd add the following:
let context = new URL(import.meta.url).searchParams.get('context');
Module C would do:
import * as S from 'service.js?context=special';
the module will be re-imported, it's basic init logic will run and we'll see in the console:
initialized in context special
Remark: I'd myself advise to NOT practice this approach much, but leave it as the last resort. Why? Module imported more than once is more of an exception than a rule, so it is somewhat unexpected behavior and as such may confuse a consumers or even break it's own 'singleton' paradigms, if any.
I believe you can use es6 module loaders.
http://babeljs.io/docs/learn-es6/
System.import("lib/math").then(function(m) {
m(youroptionshere);
});
You just need to add these 2 lines.
import xModule from 'module';
const x = xModule('someOptions');
Here's my take on this question using the debug module as an example;
On this module's npm page, you have this:
var debug = require('debug')('http')
In the line above, a string is passed to the module that is imported, to construct. Here's how you would do same in ES6
import { debug as Debug } from 'debug'
const debug = Debug('http');
Hope this helps someone out there.
I ran into an analogous syntax issue when trying to convert some CJS (require()) code to ESM (import) - here's what worked when I needed to import Redis:
CJS
const RedisStore = require('connect-redis')(session);
ESM Equivalent
import connectRedis from 'connect-redis';
const RedisStore = connectRedis(session);
You can pass parameters in the module specifier directly:
import * as Lib from "./lib?foo=bar";
cf: https://flaming.codes/en/posts/es6-import-with-parameters
I have always felt that module level states/mutable-variables are bad and ugly but I can't explain why. Here is an example of module I'm talking about:
// module with top level state
let book = null;
export function init() {
book = {
name: 'a good book',
author: 'me'
}
}
export function rest() {
book = null;
}
export function fetchBook() {
fetch('/book').then(b => book = b)
}
export function get() {
return book;
}
vs
// module with closure
export default function() {
let book = null;
return {
init: () => { /* same as module */ },
reset: () => { /* same as module */ },
fetchBook: () => { /* same as module */ },
get: () => { /* same as module */ }
}
}
The only difference I can see is since the module is resolved once, the module level state is similar to "singleton" but the closure one can be instantiated multiple time and they have their own version of the state. What are other cons/pros of the module level states?
commonJS
When using commonJS or any bundler (webpack/rollup), modules are executed by encapsulating them into a closure, passing require, module, etc as parameters and executing this function ones (caching for every require). So in the end, it comes down to the same. So whether to use module variables comes down to a question of personal preference, at least with commonJS.
(See nodejs implementation for loading cjs modules here)
ES Modules
ES Modules bring braking changes to the table:
// module.mjs
export let string = "not done";
setTimeout(() => {
res("done");
}, 1000);
// main.mjs
import { string } from "./module.mjs";
console.log(string); // "not done"
setTimeout(() => {
console.log(string); // "done"
}, 2000);
Named ES Module exports can be reassigned by exporting module. This makes modules behave more like objects than closures. Whether this is a good or a bad thing, you have to decide for yourself.
warning
Using import/export does not mean that you are using ES Modules! Babel Modules and Typescript use the same import/export syntax but under the hood transpile to commonJS. You can run the ES Modules example from above with Typescript or Babel and will get different results from running it with nodejs with ES Modules enabled.
My 2 cents
Use module variables only if you know what you are doing and are aware of the implications. It's like with equality vs strict equality.
If I have a lib, say utils.js which looks like this
exports.foo = function () {
return 'foo';
};
exports.bar = function () {
return 'bar';
};
Which can be used as follows
import {foo} from './libs/utils';
console.log(foo());
Not very spectacular, but I get the feeling that this problem is the origin of the issue described in this post. Anyway I cannot get this to work in combination with SystemJS. I have to change the code to fix it
import utils from './libs/utils';
console.log(utils.foo());
Here is my systemjs-config file:
SystemJS.config({
map: {
'plugin-babel': 'node_modules/systemjs-plugin-babel/plugin-babel.js',
'systemjs-babel-build': 'node_modules/systemjs-plugin-babel/systemjs-babel-browser.js',
},
packages: {
'.': {
defaultJSExtensions: 'js'
}
},
transpiler: 'plugin-babel'
});
So, it seems only the exports object can be loaded and not the named export. Can this somehow be fixed?
UPDATE I get the impression it could be fixed with formats
meta: {
'./libs/utils.js': {
format: 'cjs'
}
}
But so far it gives the same problems
This behavior is not SystemJS specific. SystemJS behaves like this since version 0.20 because this is what ES6 module interoperability is being standardized to.
When, as in your question, you are importing CommonJS modules (exported via module.exports) using ES6 import, you will only get the entire export, and you cannot immediately destructure the exported names.
However, when you are importing modules which are exported via ES6 export, you will be able to destructure the exported names.
So, it's all by design. Guy Bedford wrote about this on his blog and referenced the module standardization that is going on for NodeJS:
... named exports will no longer be permitted when importing a
CommonJS module from an ES module, and is discussed at
https://github.com/nodejs/CTC/pull/60/files#diff-2b572743d67d8a47685ae4bcb9bec651R217.
That is, import { name } from 'cjs.js', where cjs.js is a CommonJS
module will no longer be supported, and instead will require
import cjs from 'cjs.js'; cjs.name.
An interop workaround by using __esModule:
We will continue to support the __esModule flag in interop though,
allowing lifting of named exports for these cases.
So if the cjs.js module was written:
exports.__esModule = true;
exports.name = function () { ... }
then it would be possible to have import { name } from 'cjs.js';, even
though cjs.js is a CommonJS module, although this __esModule will
eventually in the longer term be deprecated as well.
I'm new to TypeScript and the more I read about modules and namespaces, the more it confuses me. Should I go with modules? Should I go with namespaces? should I use both? help!
I have existing javascript (.js) files that I'm trying to convert to TypeScript.
There is one .js file with some general functions and one .js file with some functions specific to filters.
Now I would like to organize this a bit more with TypeScript, as I would normally do with C#.
Is this correct usage or how should it be organized?
I'm not using a module, should I? (how?)
Company.ts
namespace Company {
// nothing yet, but in future it might.
}
Company.Project.ts
namespace Company.Project {
import Company; // like this?
let myVar : string = "something";
export function handyGeneralFunction1(foo, bar) {
// ...
}
export function handyGeneralFunction2(foo, bar, foo, bar) {
// ...
doInternalCalc();
// ...
}
export function handyGeneralFunction3() {
// ...
}
function doInternalCalc() {
// ...
}
}
Company.Project.Filter.ts
namespace Company.Project.Filter {
import Project = Company.Project; // like this?
export function initializeFilter() {
// ...
initMetadata();
// ...
}
function initMetadata() {
// ...
Project.handyGeneralFunction3();
let helper : FilterHelper = new FilterHelper("aaaa,bbbb");
let res:string[] = helper.parseData();
}
function foo() {
// ...
let x :string = Project.myVar + " else"; // can I use myVar here?
}
// a class in the namespace
export class FilterHelper {
data: string;
constructor(theData: string) {
this.data = theData;
}
parseData() : string[] {
// ...
return new Array<string>();
}
}
}
If you have the possibility to really improve the project structure, you should definitely go with modules instead of namespaces. Depending on the size of the project, this can require some effort.
Introducing namespaces could be useful if your app is not that large and you don't want to invest in switching to a different build system that can handle real modules. Using namespaces is not much more than some named global variables that contain the functions and variables. This can get confusing pretty soon as you don't have any proper dependency structures. You need to take care of importing all files in the correct order for namespaces to work correctly. The import syntax you used can even be omitted, as you anyway only reference another global object, that needs to be initialized at that point in time.
So namespaces could be your very first step if you don't have the possibility to do any larger changes on your codebase. For a future-proof setup, I would definitely suggest to use real modules. But be aware that this doesn't come for free.
For further information, I suggest to have a look at the official comment on this at https://www.typescriptlang.org/docs/handbook/namespaces-and-modules.html or the section in TypeScript Deep Dive at https://basarat.gitbooks.io/typescript/content/docs/project/namespaces.html
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