Accessing a Browserify/Babel ES6 Module with ES5 Syntax - reactjs

Context
I have an ES6/react-js file called ExternalComponent.react.jsx, with the following structure:
import React from 'react'
import Swipeable from 'react-swipeable'
const ExternalComponent = React.createClass({
//...
})
export default ExternalComponent
I have used browserify/babel to compile this file into its ES5 version (the new ES5 file is called my-external-component-in-ES5.js) using the following command:
browserify -t babelify --presets react MyExternalComponent.react.jsx -o my-external-component-in-ES5.js
The output of this file is quite large (>20,000 lines of javascript); however, it appears to wrap ExternalComponent in a large IIFE (might be wrong about this).
Problem
My goal is to access the class ExternalComponent from a pure ES5 context (in my development environment, I am unable to use ES6). I'm assuming this is going to involve one of the following:
Within ExternalComponent.react.jsx, somehow add ExternalComponent to the global namespace, so that when it compiles to ES5 I can just refer to ExternalComponent by its name.
Somehow access the ExternalComponent class which is buried in the massive my-external-component-in-ES5.js using ES5 syntax.
I'm not sure how to do either (1) or (2).
NOTE: In case anyone is wondering why I want to do this, it's because I'm trying to use the ExternalComponent within ClojureScript (which only has ES5 javascript interop; hence I have to figure out how to access ExternalComponent using only ES5 syntax!).

When you compile an ES6 module to ES5 with Browserify it converts the import syntax into CommonJS calls.
This:
import foo from './foobar';
Becomes:
var foo = require('./foobar');
You can access and use your class exactly as you would expect from your ES5 file, there's no need to clobber the global namespace. Just use the CommonJS functions.
var ExternalComponent = require('./my-external-component-in-ES5');
ReactDOM.render
<ExternalComponent />,
document.getElementById('app')
);
If you're trying to do this from ClojureScript, then I suggest creating a standalone browserify build that exposes your modules with global variables. You can use a tool like browserify-umdify.
// external-component.js
export default ExternalComponent
This will be compiled to a Javascript file that exposes externalComponent as a global variable on the window object.
Compile it to somewhere like resources/public/js/compiled/bundled-deps.js, then add it to your index.html with a script tag (above your cljs build).
Then you'll be able to reference your JS modules through the JS namespace.
(def external-component js/ExternalComponent)

Related

Import a file as a string (or source asset) in Gatsby / React

I want to import .ts, .tsx, .js, and .jsx files into a react component and render them within a PrismJS highlighting block. For example, let's say I have a TypeScript file with functionA in it that I want to highlight in my actual website:
functionA.ts:
export function functionA() {
console.log("I am function A!");
}
I want to include this in a different component. The problem is, when I import it, I am obviously importing the webpack module version of it. My weak attempt at trying to get my function render in a react component looks like this:
MyComponent.tsx:
import * as React from "react"
import { functionA } from "./functionA"
export function MyComponent() {
return (
<>
<h1>Here is your code block:</h1>
<pre>
<code>
{functionA.toString()}
</code>
</pre>
</>
)
}
and what will actually render on the page where the code block is will look something like this:
Here is your code block:
WEBPACK__IMPORT.functionA() {
console.log("I am function A!")
}
I can't exactly remember what the .toString() function output looked like, but the point is it is NOT just the contents of the file how it appears in a code edit for example - it has been modulized by WebPack.
So, in a Gatsby project, how can i get these various code snippets to be imported directly as a string, purely as they are written, without WebPack enacting its import stuff on it? Is there a plugin or some way to tell Webpack to use the imported file as its asset/source module type? I know for MD or MDX files there is the gatsby-remark-embed-snippet, but I am building a component based HTML page and can't use MD or MDX files!
It's very late, and perhaps I just can't see the forest from the trees, I know there must be a way to do this...
You need to require the file using webpack's raw-loader, i.e:
const functionA = require("!!raw-loader!./functionA");
This works for create-react-app, as in the solution discussed here, and this works for Gatsby as well!
After using require on such a file, the file contents can be rendered in the component as:
<pre>{functionA.default.toString()}</pre>
It's then up to you to add syntax highlighting using a tool like prism or similar.
Note this solution will only work as long as Gatsby V3 continues to use WebPack v4, as raw-loader is deprecated in WebPack v5 and will be phased out for asset/source type modules.

React import a module with a declared namespace

I'm trying to figure out how I can use some existing functions in my React app.
There is a javascript file called Foo.js and and associated typing Foo.d.ts
Foo.js has many functions and at the top I see
//I don't understand what the below lines re doing
var A = A || {};
A.B= A.B|| {};
A.B.C= A.B.C|| {};
A.B.C.Foo= A.B.C.Foo|| {};
(function ()
//here there are many functions
)();
Then in the d.ts file
declare namespace A.B.C.Foo
{
//here there are exported functions
}
Now I have my react components in another folder, and I wish to use the functions defined in the Foo.js file. How do I use them?
I've tried adding the below to my component
///<reference path="../../../typings/#Test/Foo.d.ts" />
My component looks like below
const Component: React.FunctionComponent<IProps> = (props:Iprops) =>
{
//I'm trying to use the function here.
}
What do I need to do to import the functions? I have no idea how to proceed.
Edit: One more piece of information:
In the html file I have a reference to Foo.js added in a script tag
" />
Edit: So I was able to get this to work. The accepted answer pointed me in the right direction with using global. Looks like Foo.js was already declared in a manner that the functions were available in React already.
On the react side, adding the below reference path worked. To get type checking what I was missing was that that the .d.ts file exports a namespace so I had to prefix all functions with that namespace like so: A.B.C.Foo.FunctionName()
///
The best way to expose functions to the other modules of your application is when a module provides a export statement:
// Foo.js
export const mySpecialFunction = () => {}
// otherFile.js
import { mySpecialFunction } from './Foo.js';
mySpecialFunction();
Otherwise, you will need to rely on window (considering your code runs on browser) to expose you function globally by import Foo.js somewhere (preferably on your app's entrypoint) and using the function:
// Foo.js
window.mySpecialFunction = () => {}
// index.js (entrypoint of your app)
import './Foo.js';
// otherFile.js
mySpecialFunction();
But about Typescript, you can't just define a namespace. It must match with a path alias you define on tsconfig.js which points to Foo.js.
The fact is that isn't clear what you are aiming.
Edit:
How would I do this "typescript as a global module attached to window"?
You could follow this documentation https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-d-ts.html. It will help you to define a global function or namespace.
Disclaimer: In a general way, do not rely on global modules as they decrease the code readibility.
Normally for a well formed library with exported functions you should add:
import FooFunctions from "A.B.C.Foo"
in your component, and then call the functions with FooFunction.{theFunctionYouWantToCall}(). You can also use this format of import:
import {theMatchingFunctionYouWantToCall} from "A.B.C.Foo"
Note: that's the normal scenario if you imported the library from npm, and it is in your node_modules. If you created this library or created the missing .d.ts file for it, make sure that Typescript is finding your .d.ts first.

Why do I have to use "require" instead of "import from" for an image in React?

I see that this answer suggests the syntax for importing images as shown below (commented out). In my case, it didn't work out (complaining there's no modules to find in that file) and I had to switch to the syntax that's currently active.
// import Author from "../assets/author.png";
var Author = require("../assets/author.png");
The difference I can imagine is that I'm using TypeScript (transpiling my TSX by awesome-typescript-loader and loading my PNG file-loader) and they seem to use JSX. But as far my understanding goes, it all transpiles to plain JS and require in the end.
Being a noob on React, I'm not sure what the reason of this discrepancy is but also I'm not sure what to google for to investigate myself.
This is more of a problem with typescript than webpack itself, you might need to declare modules on a declaration file.
Create a declarations.d.ts
Update your tsconfig.json
"include": [
"./declarations.d.ts",
],
Put this on that file:
declare module '*.png';
Error might be gone.
You can declare a module for your images like this:
declare module "*.png" {
const value: any;
export default value;
}
Then, you will be able to import your image like this:
import AuthorSrc from "../assets/author.png";
This is happening because webpack doesn't support image import out of the box. So you need to add a rule for that in the webpack config file. When you add a new rule, TypeScript doesn't automatically know that, so you need to declare a new module to resolve this. Without the module, you will be able to import images, but TypeScript will throw an error because you didn't tell to it is possible.
This issue has nothing to do with webpack or any bundler and is not quite a problem with typescript.
Typescript has stated that `require("path") is a way to include modules to the scope of your current module, whilst it can be also used to read some random files (such as json files, for example).
As Vincent and Playma256 specified, you can declare a module wildcard to match certain file types, so you can import it as an import statement. But you don't really need to do this. Typescript won't give you an error if you are trying to import a png or a json file (tslint might, but that depends on your configuration).
By the way, if your declaration is within the source folder of your project as defined in tsconfig.json, you don't need to include it as specified by Playma256.
I've created a sample project in node for you to test:
https://github.com/rodrigoelp/typescript-declare-files
I think you can solve this problem with Webpack&&typescript.The official webpage of webpack has introduced something about this in
https://webpack.js.org/guides/typescript/
And I have try this myself in
https://github.com/reactpersopnal/webpack-root/tree/feature/typescript
The reason is that you would like to use non-code assets with TypeScript, so we need to defer the type for these imports for webpack.
Your could simply add custom.d.ts.
declare module "*.jpg" {
const content: any;
export default content;
}

Including an external js script in a React Component

I have a react application, managed using react-scripts.
In my app i am using an external js script. The external script doesn't do any module exports.
The structure of the external script is as below (too big to include it in full).
var TradingView = {.... various functions }
At the end of the file:
if (window.TradingView && jQuery) {
jQuery.extend(window.TradingView, TradingView)
} else {
window.TradingView = TradingView
}
My goal is to create a simple react component using the external script, and call the function: TradingView.widget({...});
I have been searching online for ways to include an external script in a react component/ES6 style, and have tried various options: react-async-script-loader, and various webpack plugins: script-loader, imports-loader, ProvidePlugin etc. But i haven't been able to make it work.
The error i am getting after using the imports-loader or ProvidePlugin is:
1189:31 error 'jQuery' is not defined no-undef
1190:9 error 'jQuery' is not defined no-undef
In my webpack config, i have:
In the loaders section:
{
test: /tv\.exec\.js/,
loader: 'imports?jQuery=jquery,$=jquery,this=>window'
}
In the plugins section:
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
})
When i create a simple webpage (no react), and include the script, and call TradingView.widget(), the page just works fine.
The closest i could get help was to look at:
Managing jQuery plugin dependency in webpack
But it didn't work for me. I am quite new to the react, webpack ecosystem, so I am not sure what i am missing here.
Please let me know if you need any additional info.
Update: I tested the above, including the script in a simple react component, but not using the react-scripts this time, and directly using webpack configurations. I was simply able to import the external js in my component directly, and it worked. I was also able to use imports-loader plugin in webpack to expose jQuery, which also worked. So its possible that using react-scripts needs something else to make it work.
It looks like your external script is handling its "exports" by adding them as members of window. You can use the import keyword on a file that doesn't define exports like so:
import "modulename";
There's nothing special about that syntax except that it doesn't imply that any functions or variables will be made available via the import facility. The code in "modulename" that assigns members to .window will execute, which is the important thing.
For compiler complaints about accessing window.* globals, try assigning the variable you want to access to a local variable:
const jquery = window.jquery;
...or maybe...
const TradingView = window.TradingView;
Then you'll have the variable in scope, and it should be usable.

How to make it possible to use Typescript with SystemJS and Angular?

I'm trying to make SystemJS work with Typescript, but they seem to conflict with each other.
How can I take advantage of the autoloading from System.js without it conflicting with keywords on Typescript? using import / require makes Typescript using it's own way for loading and referencing files, although it translates export as module.exports = ..., it doesn't do the same for import
Is it possible at all to achieve this or I'll have to wait for Typescript to support ES6 keywords?
TypeScript 1.5 adds support for compiling to ES5 SystemJS module syntax.
Author a class like:
export class Foo {}
Then compile with
tsc --module system foo.ts
The result will be an ES5 module using the SystemJS format.
In TypeScript, you would write the following import statement...
import dep = require('dep');
console.log(dep);
When you compile, you pass the module flag:
tsc --module commonjs app.ts
This tells TypeScript to target CommonJS style modules (it can also target AMD if needed - SystemJS supports both styles of syntax).
The output looks like this:
var dep = require('dep');
console.log(dep);
This output is analogous to the following example from the SystemJS documentation.
// library resource
var $ = require('jquery'); // -> /lib/jquery.js
// format detected automatically
console.log('loaded CommonJS');
If you need more help, you can ask a question and include specific examples that demonstrate the issue and we will be able to give more specific advice.
you can see here the way I did it with traceur instead of Typescript, but it should work pretty much the same with Typescript, I'll add ts as soon as I can play again with it.
NOTE:Is more of self-reminder or a playground than a proper seed
As Steve mentioned the strongest point of SystemJs is that you can use almost any module definition , the loader should detect the module format, I personally prefer to declare the modules as in
define([deps...],(deps..){
// ...
})
I find it to be like constructor injection pattern from other languages and frameworks and it always translates to the same Javascript, beacuse it is Javascript , beautified with class and arrow functions (+anotated with types the case Typescript).
Also choosing amd shown clearly asynchronous intentions, that would be honor anyway if you choose lets say ES6 module syntax, beacuse the code after the import sentence will only executed when the dependencies have finished loading. pretty much like the async keyword it feels a little too cryptic for the non initiated
BTW: and OutOFcontext: Cheers from SA / JHB to the amazing work of SystemJS dev Mr. Bedford

Resources