How to import "old" ES5 code in ES6 - javascript

I have an ES6 application (with Babel 6.5 and Webpack) and it successfully imports my modules like this:
import $ from 'jquery';
I wanted to install https://github.com/robflaherty/riveted/blob/master/riveted.js (a plugin for Google Analytics), but as you can see, the code doesn't have something like module.exports = ..., it only defines a global variable riveted, but it has an apparently valid package.json pointing to riveted.js.
So doing something like
import riveted from 'riveted'
riveted.init();
throws an error:
_riveted2.default.init is not a function
import riveted from 'riveted'
riveted.init();
import 'riveted'
riveted.init();
throws an error:
riveted is not defined
import * as riveted from 'riveted'
riveted.init();
throws an error:
riveted.init is not a function
How can I access riveted's init() function?

You can use the webpack exports loader:
var riveted = require("exports?riveted!riveted")
See the shiming modules overview for details

Step 1. modify riveted.js
Add some code after line 18.
// Browser global
root = !root && typeof self == 'object' ? self : root; // add this line
root.riveted = factory();
Because the this is undefined when the file is imported by es6, we use self instead.
self is better than window, it works in both main thread and worker.
Step 2. modify your import path
like this:
import './riveted.js';
riveted.init();
The ./ or ../ is required for import js file directly.
Examples:
import `./file.js`
import `../file.js`
import `./a/b/file.js`
import `../a/b/file.js`
import `../../a/b/file.js`
Tested in chrome.

Related

Import javascript from node_modules in angular

I installed a npm package which contains a javascript file, what I want to use. The js file name is all.js and contains this code:
import { initExtended } from './extended'
import Header from './components/header/header'
function initAll(options) {
// Set the options to an empty object by default if no options are passed.
options = typeof options !== 'undefined' ? options : {}
// Allow the user to initialise GOV.UK Frontend in only certain sections of the page
// Defaults to the entire document if nothing is set.
var scope = typeof options.scope !== 'undefined' ? options.scope : document
// Find first header module to enhance.
var $toggleButton = scope.querySelector('[data-module="govuk-header"]')
new Header($toggleButton).init()
initExtended(options)
}
export {
initAll,
Header
}
File all.js is located in node_modules.
When I tried to import it directly from index.html like:
<script type="module" src="node_modules/#id-sk/frontend/govuk/all.js"></script>
It is not working. Console error, file not found.
I also tried import it via angular.json:
"scripts": [
"./node_modules/#id-sk/frontend/govuk/all.js"
]
Also not working with error "Uncaught SyntaxError: Cannot use import statement outside a module (at scripts.js:15241:1)". The error refers to line:
import { initExtended } from './extended'
I also tried to import it in polyfills but I don't know how to call it.
As you are speaking about angular.json, I assume that you are working in an Angular application bootstrapped using the Angular CLI with default settings.
To be able to use this package #id-sk/frontend in your typescript files, you have to import it directly into your typescript file.
1. Import #id-sk/frontend in your TS files
// Import everything into a local variable
import * as govuk from '#id-sk/frontend';
// Import specific object
import { HeaderExtended } from '#id-sk/frontend';
2. Run ng serve
⚠ Spoil: It will lead to typings errors
3. Let's add or create typings
As #id-sk/frontend is not written in typescript, the compile doesn't know about the typings of this library.
Following this statement, you have two choices:
Find or contribute to DefinitelyTyped in order to create the typings of your package #id-sk/frontend
Create a local file typings.d.ts in your ./src folder to declare an empty module
declare module "#id-sk/frontend"
4. Kill & run ng serve again
Enjoy it!
Go further
You can add typings to your module in order to give you autocompletion on the provided objects of #id-sk/frontend.
``ts
declare module "#id-sk/frontend" {
export interface Options {
scope?: Document
}
export function initAll(options: Options): void;
}

How do you find out which functions are exported by a node/npm package?

Following my earlier question, and the Mozilla documentation on import, I now understand that I must do something like the following to use the functionality in a module:
import * as name from "module"; or
import {functionName} from "module";
Coming from using CommonJS, I never thought about which functions were exported by a package because I just used to require them like:
const vueServerRenderer = require('vue-server-renderer') // get the module
vueServerRenderer.createRenderer() // use a function in that module
How can someone find out which functions are being exported by a module such as express or vueServerRenderer so I know how to use the correct import statement like:
import express from 'express' instead of import * as express from 'express'?
You need to read the module source.
Every export statement exports something. It may be a function, an array, a string, a class etc.
Every export statement without default needs to be destructured on import:
import { NonDefaultThing1, NonDefaultThing2 } from 'somewhere'
An export statement with default must be imported directly without the {}:
import DefaultThing from 'somewhere'
Some modules have default export but also non-default exports. You can pick and choose what to import:
import DefaultThing, { NonDefaultThing7 } from 'somewhere'
If you use an IDE that can parse javascript such as Microsoft Visual Studio Code you can get autocompletion/intellisense of the import statement. There are even plugins that does auto-import: just use a class or function or something from a module and it will automatically add the required import statement at the top of your file.
TLDR: The default export.
Say a particular library named "module" has the following code
function functionName() {
// function body
}
export default functionName;
Now, in your code, if you put
import blah from "module";
then blah will point to functionName.

NodeJS ES6 Modules and "default" export / import

I am trying to use ES6 style modules with NodeJS v13.13 and I am running into conflicting behavior.
Originally my project was transpiled with node-babel, but then I enabled the builtin ES6 module support by adding "type": "module" to my package.json file.
At this point, import statements such as import * as esprima from 'esprima'; stopped working correctly. Upon examination, rather than the import creating the function esprima.parse, it was creating a function esprima.default.parse.
Of course this was all fixable by using something like:
import * as esprimaLoad from 'esprima';
const esprima = esprimaLoad.default;
However, what behavior is correct per the spec? Is babel right to strip out / collapse the default object, and is there a bug in the current node.js behavior which is still labeled as experimental? All of this ES Module vs CommonJS module stuff gives me a headache.
Update:
I still haven't gotten to the bottom of this, but there is more involved. esprima.js, one of the libraries where this was an issue, appears to have been created using webpack and is a UMD (universal module definition) format. It starts with the following:
(function webpackUniversalModuleDefinition(root, factory) {
/* istanbul ignore next */
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
/* istanbul ignore next */
else if(typeof exports === 'object')
exports["esprima"] = factory();
else
root["esprima"] = factory();
})(this, function() { ...
I think the webpack UMD format is already meant to work with both import statements in the browser and require statements in node.js. However, when using the experimental modules / babel in node.js it seems like the following does not work at all:
import esprima from 'esprima'
and the following works with babel but not the experimental modules:
import * as esprima from 'esprima'
and the following works with both babel and the experimental modules:
import * as esprimaLoad from 'esprima'
const esprima = esprimaLoad.default?esprimaLoad.default:esprimaLoad;
For modules that don't use this webpack UMD trickery, both the babel and the experimental modules seems to work just fine.
If you are exporting default then you don't need to import it in that roundabout fashion.
The simplest version directly imports the default:
import myDefault from '/modules/my-module.js';
It is also possible to use the default syntax with the ones seen above
(namespace imports or named imports). In such cases, the default
import will have to be declared first. For instance:
import myDefault, * as myModule from '/modules/my-module.js';
// myModule used as a namespace
You can check out more from MDN
So in your case
import esprima from 'esprima';
This should be simplest import statement.

Why is TypeScript adding .default to a globally defined import?

I have an external library thing.d.ts file with a global definition inside:
declare var thing: ThingStatic;
export default thing;
I reference npm module in my TypeScript:
import thing from 'thing';
...
thing.functionOnThing();
When I transpile the TS (targeting ES6) it looks something like this:
const thing_1 = require("thing");
...
thing_1.default.functionOnThing();
This then throws an error:
Cannot read property 'functionOnThing' of undefined
Why is TypeScript adding .default between thing_1 and functionOnThing()?
There is no property named default on ThingStatic, and no default property on the underlying JS object that the .d.ts file defines.
Why is TypeScript adding the property and how do I stop it?
import thing from 'thing';
This line of code means "import the default export from the module 'thing' and bind it to the local name thing".
TypeScript does as you requested and accesses the default property of the module object.
What you probably meant to write was
import * as thing from 'thing';
This appears to be a bug with global TS definitions and "module": "commonjs" in the tsconfig.json.
You can either use global TS definitions and stitch all your output into a single file, or you can use modules and directly import them.
The error here is due to the require returning the module context, and the name of the default being irrelevant - it always becomes default...
declare var thing: ThingStatic;
export thing; // Explicit export for thing
export default thing; // Default export for thing
Now require will return this context, so with commonjs modules:
import module from 'thing';
var thing = module.default; // From the default export
var alsoThing = module.thing; // From the named export
However, I've found this to be inconsistent, so switched to es6 modules:
import thing from './thing'; // Import default
import { thing } from './thing'; // Import named
const thing = (await import('path/to/thing.js')).default; // Import dynamic

ES6: share data between modules

To share data between modules, a usual pattern is to capsulate the data into a common module and import it in other modules.
In my case the data to be shared is a logger, which need to be initialized before used. I call init() at the entry point of my application.
// main.js
let logger = require('#my/logger');
logger.init({...});
let xxx = require('./moduleX');
let yyy = require('./moduleY');
In other modules, the initialized logger can be used:
// moduleX.js
let logger = require('#my/logger');
const log = logger('moduleX');
function x() {
log.debug('some msg');
}
Above code works well in node.js. But if I change to ES6 module syntax, it doesn't work because ES6 module import is hoisted.
// main.js
import {logger} from '#my/logger';
logger.init({...}); // this line is run after import moduleX
import {x} from './moduleX';
// moduleX.js
import {logger} from '#my/logger';
const log = logger('moduleX'); // logger is not initialized !
export function x() {
log.debug('some msg');
}
With ES6 module, how can I initialize some data and share them to other modules?
There was a similar question but the answer doesn't fit my case.
Update:
Some answers suggest to put the code which access shared data into function so that the code isn't invoked immediately at module load. But what if I really need to access it during module loading? I updated my code to demonstrate the use case -- it would be too trivial to call logger(name) in every function if not make log as module scope const.
Finally I solve it in the way that #PaoloMoretti mentioned in his comment.
Write a module in my app to init the logger for my app:
// logger_init.js
import {logger} from '#my/logger';
logger.init({...});
Import the initialization module once at the entry point of application, prior to imports of any other modules that use logger as well. It guarantees that the initialization is done before loading other modules.
// main.js
import './logger_init';
import {x} from '#my/other_module';
import {y} from './module_in_myapp';
Other modules can use initialized logger directly:
// #my/other_module
import {logger} from '#my/logger';
const log = logger('moduleX'); // logger has been initialized
export function x() {
log.debug('some msg');
}
The dependency tree is:
<init>
myapp --+--> logger_init ------------> #my/logger
| <use> ↑
+--> module_in_myapp -----------+
| <use> |
+--> #my/other_module ----------+
Why I don't adopt the way that add a wrapper module which init and return a logger (as Bergi's answer) is because the modules uses logger could be reusable modules not in my application.
Try to provide some entry points to your xxx.js and yyy.js modules or even make them as functions.
// main.js
import {logger} from '#my/logger';
import * as xxx from './xxx';
logger.init({...});
xxx.run();
// xxx.js
import {logger} from '#my/logger';
export function run () {
logger.debug('In xxx');
}
You could have each module that has a dependency on some initialisation routine return a function that can be executed manually to invoke its functionality, rather than expose that functionality as an immediate side-effect of importing it. For example, in xxx.js:
// xxx.js
export default function (log) {
log('xxx');
}
Then place all initialisation operations within an entry file and invoke the modules defined in the above way after these operations are complete:
// main.js
import xxx from './xxx';
import {logger} from '#my/logger';
logger.init({...});
xxx(logger);
But what if I really need to access it during module loading?
Please see the amended code examples above. Pass the instance of logger to the function exported by each module.
A logger, which need to be initialized before used. What if I need to access it during module loading?
Make an extra module with an initialised logger:
// main.js
import {x as xxx} from './moduleX';
import {y as yyy} from './moduleY';
// logger_ready.js
import {logger} from '#my/logger';
logger.init({…});
export default logger;
// moduleX.js
import logger from 'logger_ready';
const log = logger('moduleX'); // logger is initialized here!
export function x() {
log.debug('some msg');
}
After experiments, found that the BroadcastChannel api is perfect to share data or events easily between different es6 modules, tabs, workers, frames...
We can pass objects or arrays by using json stringify there too:
To send datas:
new BroadcastChannel('myapp_section8').postMessage('Hello world!')
Example receiving, from another module:
new BroadcastChannel('myapp_section8').addEventListener('message', function(e){
console.log(e.data)
})
It avoid to use any DOM elements to dispatchEvent, it simplify stuffs a lot!

Categories