React.NET, Webpack - 'ReactDOM' is undefined (client side) - asp.net-mvc

Following the tutorial here, I have managed to get webpack doing my bundling for me, and it is working for pre-rendering on the server side, e.g.:
#Html.React("Components.myComponent",
New With {
.initialData = Nothing
})
#Html.ReactInitJavaScript()
This is correctly displaying my component in the browser, but as soon as the client takes over I get the error 'ReactDOM' is undefined
I have installed the react-dom using npm install react-dom --save-dev
I have tried to require the ReactDOM, first in the .jsx file containing my component, then in the client.js file that webpack is building from:
//myComponent.jsx
var React = require('react');
var ReactDOM = require('react-dom');
or
//client.js
var ReactDOM = require('react-dom');
var Components = require('expose?Components!./src');
But I am still getting the same error.
If I add the react and react-dom scripts directly above my webpack compiled client javascript, then the problem goes away:
//index.html
<script src="https://www.facebook.com/react-0.14.0.min.js"></script>
<script src="https://www.facebook.com//react-dom-0.14.0.min.js"></script>
<script src="~/Scripts/webpack/build/client.bundle.js"></script>
So how can I get webpack to properly include these scripts in the client bundle?
EDIT
I did have the following externals in my webpack.config.js, but removing them doesn't seem to make any difference.
externals: {
// Use external version of React (from CDN for client-side, or
// bundled with ReactJS.NET for server-side)
react: 'React'
}
I guess, externals are there so you can use CDN based scripts, so maybe I am overthinking this one and should just leave the CDN based react scripts in my view?

You should expose react-dom globally in your client.js, so it will be available in browser in ReactDOM variable to render client-side:
require("expose?ReactDOM!react-dom");

Related

TinyMCE w/Rails 6 and webpacker - no icons, turbolinks

We are trying to upgrade our app to Rails 6 with Webpacker (and Stimulus). Things are going fine except for TinyMCE. We have 2 problems, I will ask them in separate SO questions.
We installed TinyMCE using
yarn add tinymce
and have version 5.3.0
In our stimulus controller header we have:
import tinymce from 'tinymce/tinymce';
import 'tinymce/themes/silver';
import 'tinymce/skins/ui/oxide/skin.min';
import 'tinymce/skins/ui/oxide/content.min';
import 'tinymce/plugins/paste';
import 'tinymce/plugins/link';
And then in the controller connect block we have:
connect() {
console.log('gonna reload');
require.context(
'!file-loader?name=[path][name].[ext]&context=node_modules/tinymce&outputPath=js!tinymce/skins',
true,
/.*/
);
tinymce.init({
selector: '.tinymce',
plugins: ['paste', 'link'],
skin: false
});
}
This is code that essentially works, except that in the console I see:
VM40 application-68201fac0dcbbcb543e0.js:213771 GET https://xxx.ngrok.io/packs/js/icons/default/icons.js net::ERR_ABORTED 404 (Not Found)
VM40 application-68201fac0dcbbcb543e0.js:224775 Failed to load icons: default from url https://xxx.ngrok.io/packs/js/icons/default/icons.js
Do we need another require.context to handle the loading of those?
It looks like TinyMCE no longer loads the icons dynamically, so Webpack must be instructed to include them in the bundle manually. Adding the icons import after the tinymce import worked for me:
import 'tinymce/icons/default';

Generate seperate build file for a particular component in angular 7

I am going to develop a very large application using Angular 7 Framework.
I have created a blank angular workspace using
ng new angular-app --create-application=false
And in this workspace I have two angular applications created using the following commands:
ng generate application app-one
ng generate application app-two
Inside each of the two applications, I am going to have multiple components each working independently of each other.
I am looking for a way to create a separate javascript build file for each of the component so as to reduce the build size.
And use each of the separately build js files to use each component as a web component.
Please read what I have already tried to get a better idea.
I have tried the following steps:
Create a repository with prefix custom for custom angular elements:
ng new app-name --prefix custom
Add the angular elements package:
ng add #angular/elements
Create custom element component with encapsulation as native/emulated/none as required:
ng g component my-component --inline-style --inline-template -v Native
Define the custom element in app.modulte.ts
import { Injector} from '#angular/core';
import { createCustomElement } from '#angular/elements';
...
export class AppModule {
constructor(private injector : Injector){
const el = createCustomElement(MyComponent, {injector : this.injector});
customElements.define('my-component',el);
}
ngDoBootstrap(){ }
}
Install ngx-build-plus package for building a single bundle (e. g. for Angular Elements):
npm i ngx-build-plus
Update application's builder section within the angular.json file so that it points to ngx-build-plus:
"builder": "ngx-build-plus:build",
Add script in package.json to run builder:
"build:ngx": "ng build --prod --output-hashing none --single-bundle true"
If required, Combine scripts.js and main.js in the created dist folder by creating a js file "concat_ngx.js":
const fs = require('fs-extra');
const concat = require('concat');
(async function build() {
const files = [
'./dist/<your_project>/scripts.js',
'./dist/<your_project>/main.js',
]
await fs.ensureDir('elements_ngx')
await concat(files, 'elements_ngx/combined-script.js');
})()
Run file to get single js file:
node concat_ngx.js
Use js file in any Angular/Other project to use the custom component created.
But the problem here is I have to change the component bootstrap every time in app-module.ts
I needed an automated way to change the bootstrapping in app-module.ts at runtime.
But the problem here is I have to change the component bootstrap every time in app-module.ts
I needed an automated way to change the bootstrapping in app-module.ts at runtime.
In Angular 7, Add Default or Automatic bootstrapping :-
It is the default way an angular application bootstraps and main.ts holds the starting point of an application.
platformBrowserDynamic().bootstrapModule(AppModule);
For more information see here: -https://medium.com/learnwithrahul/ways-of-bootstrapping-angular-applications-d379f594f604
ng serve -o app-one
or
ng serve -o app-two
For more information see here https://medium.com/#klauskpm/change-the-default-angular-project-27da8fca8721

How to migrate requiring of select2.js from sprockets to webpacker

I'm in the process of migrating a Rails 5.1.5 project, which uses CoffeeScript, from using sprockets to using webpacker. The project also uses select2.js. With sprockets, I did the following:
Install jquery-rails (jQuery is a dependency for select2).
Put select2.js code in vendor/assets/javscripts.
In application.js.coffee, add:
#= require select2
After that I was able to use select2 to in my application.js.coffee file:
$(document).on 'turbolinks:load' ->
$('select').select2
So far I've described the pretty standard way of including/using javascript libraries with sprockets.
However, with webpacker I can't make select2 work and I'm not sure why. I have two hypothesis:
I'm not importing/requiring it properly;
it doesn't find jQuery at some point of the load process;
So for jQuery, I did the following:
yarn add jquery
included in my environment.js:
const webpack = require('webpack');
environment.plugins.append('Provide', new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
}));
I've removed the jquery-rails gem, as well as #= require 'jquery' and tested that jquery works, so I guess I have correctly included it. However, I tried several ways of importing select2 (using es6 imports) and none of them worked. I tried:
import select2 from 'select2';
import select2 from 'select2/dist/js/select2'
import select2 from 'select2/dist/js/select2.js'
import 'select2/dist/js/select2.js'
I even tried to import it from the old vendor location by writing inside app/javascript/pack/application.js.coffee:
import '../../../vendor/assets/javascripts/select2'
I can confirm that the file contents is imported, as I put a console.log within the select2 file under node_modules/select2/dist/js/select.js and it did get printed. However, I also get the error TypeError: $(...).select2 is not a function when I execute $('select').select2() in the browser's dev tool console.
What am I missing or doing wrong?
P.S. I can provide much more info, but I didn't want my question to get any more bloated.
P.P.S. With my modest JS knowledge, I looked at the source code but couldn't recognize what exactly they are exporting and how am I supposed to import it.
I know this is an old post, but just in case someone else could benefit:
app/javascript/packs/application.js
...other requires...
require('select2')
window.Rails = Rails
import 'bootstrap'
...other imports...
import 'select2'
import 'select2/dist/css/select2.css'
$(document).on("turbolinks:load", () => {
$('.select2').select2()
})
My similar problem
I have stumble upon the same problem with another web component (Switchery):
I imported the component with yarn add switchery (no error)
I could import it correctly through WebPack with import 'switchery' (no error bundling the pack)
But when I was trying to use the Switchery object in the browser like they say in the doc:
var elem = document.querySelector('.js-switch');
var init = new Switchery(elem);
I would get the error: ReferenceError: Switchery is not defined
Note: I didn't want to install RequireJS as WebPack is supposed to do the same thing (and even better) nowadays.
My solution:
The problem was the webpack doesn't expose the pack-generated variables and classes in the global scope!
So to fix this, I needed to do two things:
Explicitly give a name to the imported class from Switchery:
import Switchery from 'switchery'
Use this Class only in the same JS file where the import was done
Testing hack:
If you want to try that out and "go back" to the mess that sprocket allowed, in the same file, you can expose "globally" the variable so you can use in from the browser:
import Switchery from 'switchery'
window.Switchery = Swicthery
now you can execute the switchery almost like in the example:
var init = new window.Switchery(elem);
Hope that helps...

Aurelia: using es6 import for electron + typescript

I have an aurelia application running in electron. My source files are typescript and I have ambient typings for electron and node.
Because I know I'm compiling for use on electron, I am transpiling my typescript to es6 and with System module loading; this means I can turn system.js's transpiler off. I'm using system.js and jspm because that is approach Aurelia has been pushing.
So in my ts files: I would like to be able to do:
import {remote} from 'electron';
Unfortunately, system.js does not know anything about the module electron and fails during runtime. TypeScript on the other hand is perfectly happy because I've set up the typings for electron and node; I get full intellisense in VSCode too.
note: if you attempt to do var electron = require('electron'); in the header, system.js interferes with it and it fails to load. You can place that 'require('electron')' within a class or function and it will work, but I don't find this ideal.
Question: How can I get system.js to correctly return the 'electron' module that is only available when you run the app in electron itself?
A solution --hopefully there is a better way-- I've come up with is to shim the electron module for system.js and link it directly to the contents of require('electron'):
electron.js
System.register([], function (exports_1, context_1) {
"use strict";
var __moduleName = context_1 && context_1.id;
var electron;
return {
setters: [],
execute: function () {
electron = require('electron');
exports_1("default", electron);
Object.keys(electron).forEach(function (key) {
exports_1(key, electron[key]);
});
}
}
});
this effectively wraps the internal electron module and allows system.js to know about it. It works; but hopefully there is a more elegant/built-in way that others know of.
You don't need any mappings or changes to the typescypt as import {remote} from 'electron' will attempt to resolve electron.js as a last resort.

Using Browserify with Reactjs

I am just getting started on a new project which I would like to write in ReactJS. I am trying to use Broserify to bundle everything so that I can have it in multiple js files.
However, when I try to bundle my react file (browserify main.js > bundle.js), I get this error:
"Error: Parsing file /Users/Kathleen/Documents/Referral_Site/main.js: Unexpected token (3:4)"
main.js looks like this:
var Lander = require('./lander');
React.render(
<Lander />,
document.getElementById('content')
);
What's breaking the parser? Thanks in advance!
EDIT: also, I included react in the surrounding html like this:
<script src="http://fb.me/react-0.12.2.js"></script>
is there some way that I need to include it in main.js as well?
You must compile JSX to plain JS first, there is plugin for browserify.
https://www.npmjs.com/package/reactify

Resources