TL;DR How can I use esbuild with ReactJS.NET?
Long version
ReactJS.NET expects the following from a "bundle":
React.Exceptions.ReactNotInitialisedException: 'React has not been loaded correctly: missing (React, ReactDOM, ReactDOMServer). Please expose your version of React as global variables named 'React', 'ReactDOM', and 'ReactDOMServer'
What Webpack actually does is always quite unclear to me, but looking at the ReactJS.NET Webpack tutorial here, this is the setup:
import React from 'react';
import ReactDOM from 'react-dom';
import ReactDOMServer from 'react-dom/server';
import RootComponent from './home.jsx';
global.React = React;
global.ReactDOM = ReactDOM;
global.ReactDOMServer = ReactDOMServer;
global.Components = { RootComponent };
Further at the sample webpack-config here, there is this output-setting:
{
[...]
globalObject: 'this',
}
Mirroring this in esbuild would be using iife and esbuild.globalName = 'this', but it throws an error:
> (global name):1:0: error: Expected identifier but found "this"
1 │ this
Esbuild is quite stern on that it is a bundler (not a transpile-and-concatenator), so how would I tweak Esbuild to have the bundle mutate the global object to what React.NET expects? - And is it even possible?
Thanks,
Flemming
Found a solution.
import { build } from 'esbuild'
const globalName = 'whatever'
build({
[...]
format: 'iife',
globalName,
footer: {
// Important! Assigns the raw export of the bundle to whatever `this` is in the given context
js: `Object.assign(this, ${globalName})`,
},
})
Related
Our application uses TypeScript's namespaces and do not use any kind of modules. We want to use react-datepicker, which is written using modules.
For example, it contains the next code:
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import onClickOutside from 'react-onclickoutside';
import moment from 'moment';
import { Manager, Popper, Target } from 'react-popper';
...
export default DatePicker;
Or transpiled:
var React = _interopDefault(require('react'));
var PropTypes = _interopDefault(require('prop-types'));
var classnames = _interopDefault(require('classnames'));
var onClickOutside = _interopDefault(require('react-onclickoutside'));
var moment = _interopDefault(require('moment'));
var reactPopper = require('react-popper');
...
exports['default'] = DatePicker;
It is required to build some js-bundle for this package, that will contain react-datepicker itself as global variable DatePicker, and all its dependecies, besides react, moment, classnames.
These libraries are already added to the application as global variables (React, moment, classNames), so it should use these global variables.
Is there some plugins and techniques for rollup, webpack, etc. that can help to build such bundle?
You can achieve this with rollup, you would need to use its external and globals option like this:
external: ['react', 'react-dom', 'moment', 'classnames'],
globals: {
'react': 'React',
'react-dom': 'ReactDOM',
'classnames': 'classNames',
'moment': 'moment'
},
Depending on the source file you're using, you might need to use these 2 plugins:
import commonjs from "rollup-plugin-commonjs";
import resolve from 'rollup-plugin-node-resolve';
Also, you might need to use rollup-plugin-replace because react-datepicker seems to include process.env.NODE_ENV in its code, and you will need to remove that.
In case you would like to see a full working example, check this repo I created:
https://github.com/mxcoder/rollup-iife-react-datepicker
If I wanted to do this in all my test.js files:
import { shallow } from 'enzyme';
import MockAdapter from 'axios-mock-adapter';
Is there a way to globally import it so that every 'tests.js' file will automatically have that imported?
Thanks in advance!!!
You can use globals!
Example:
setup.js
import { _shallow } from 'enzyme'
import _MockAdapter from 'axios-mock-adapter'
global.shallow = _shallow
global.MockAdapter = _MockAdapter
test1.js
describe('My Test 1', _ => {
MockAdapter() // Use it!
})
Notes:
Global variables will solve this particular issue. But the caveat is that you will have one instance across each test (which might be ok depending on the dependencies you import)
EDIT: In context of react-boiler-plate
If you read package.json, you'll see that there is a test setup file within "jest".
This path is react-boilerplate/internals/testing/test-bundler.js.
Edit that file with the contents of setup.js (aforementioned)
Now each one of your tests should have the variables defined
We would like to create bundle.js from React and webpack, where a per build configuration is included and made available to the React code, but we are not sure how to go about doing this.
The idea would be to be to do something like:
npm run build -- config="config123.json"
And then have the generated bundle.js include that configuration, such that it could be used by the root container. Something like:
import React from 'react';
import ReactDOM from 'react-dom';
import RootContainer from './containers/main-container';
ReactDOM.render(
<RootContainer config={configPassedByBuildProcess}/>,
document.getElementById('app')
);
Is this possible and if so, how should we approach this?
No, I don't believe that you can pass a file on build, from what I know the only thing that you can pass is a string like an ENV variable. You could import the config123.json file like this
import React from 'react';
import ReactDOM from 'react-dom';
import RootContainer from './containers/main-container';
import config from '/path/to/config123.json'
ReactDOM.render(
<RootContainer config={config}/>,
document.getElementById('app')
);
You may need a JSON loader for importing JSON files
After a bit of experimenting, the solution I have found is to use Webpack's DefinePlugin, since this provides a way of doing string substitution during webpack's build phase.
In my React code I defined:
// Will be substituted by webpack's DefinePlugin. Not an error.
const bundleConfig = BUNDLE_CONFIG;
and then in my webpack.config.js I first load my config (hard coded for now):
// The path can be pulled in via 'process.args' and parsed as appropriate
const bundleConfig = fs.readFileSync('bundle-config/default-config.json', 'UTF-8');
module.exports = {
plugins: [
new webpack.DefinePlugin({
'BUNDLE_CONFIG': bundleConfig
})
]
}
Note that if for some reason we want to build with a missing or undefined bundleConfig, then the right substitution would likely be the string (not the keyword) 'undefined' , to keep the code valid, such that:
const bundleConfig = 'undefined';
module.exports = {
plugins: [
new webpack.DefinePlugin({
'BUNDLE_CONFIG': bundleConfig
})
]
}
Just one extra thing was the configuration of the package.json (scripts section only):
{
"scripts": {
"build": "run() { NODE_ENV=production && webpack -p $1; }; run",
}
}
This allow passing in the path to the webpack.conf.js script, when we call:
npm run build /path/to/config.json
I have a module that imports using ES6 object destructuring.
foo.js:
import { render } from 'react-dom';
I want to mock render in a unit test. I've tried two things, but neither of them seem to work.
Using a simple stub:
import * as reactDom from 'react-dom';
let renderStub = sinon.stub(reactDom, 'render');
Someone suggested proxyquire, but I'm not sure of the correct way to stub a method from a node module. This is what I attempted:
import proxyquire from 'proxyquire';
proxyquire.noCallThru();
let foo = proxyquire('./foo', {
'react-dom': {
render: () => {}
}
});
How do I mock this method correctly?
I have an App.js file like this:
import './bootstrap';
if (document.getElementById('vue')) {
new Vue({
});
}
It imports a bootstrap javascript file which holds the Vue npm package(node module).
In my bootstrap file I import it like so:
import Vue from 'vue';
When I run eslint with this setup though I get told:
'Vue' is not defined.
If the eslinter only checks per file this seems really obvious since the actually Vue variable is defined in a file that is imported. Can this be fixed cleanly though or do I have to edit my .eslintrc.js for a case like this?
I believe ES6 imports only apply to the current file (which is the main benefit of a module system – to avoid global contamination). Importing a module without bindings won't also make that module's imports available; they remain scoped to that module only.
You have a few options:
You can explicitly import it everywhere you need it (the intended way with modules).
import Vue from 'vue';
You can export Vue (and anything else) from your bootstrap file and import everything:
In bootstrap.js:
import Vue from 'vue';
export { Vue };
In App.js:
import * as bootstrap from './bootstrap';
const Vue = bootstrap.Vue;
You can make Vue global from in your bootstrap file, but it defeats the benefit of modules:
window.Vue = Vue;
The import and export articles at MDN give a good overview of different possible ways of importing and exporting.
You can try few configuration of eslint in .eslintrc to get this working. This error is coming with es6-modules, you can try playing with following config:
{
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"rules": {
"semi": 2
}
}