SCSS compilation in an isomorphic React app - reactjs

I'm writing an isomorphic React app based on :
https://github.com/choonkending/react-webpack-node
Instead of css modules used in the examples I'd like to use scss though. And for some reason I'm having a really hard time getting them to work. My first step was to remove the css webpack loaders from both the server and the client configs replacing them with scss-specific loaders (as well as removing postcss) :
loaders: [
'style-loader',
'css-loader?modules&localIdentName=[name]_[local]_[hash:base64:3]',
'sass-loader?sourceMap',
]
But this throws ReferenceError: window is not defined when building as style-loader is apparently not suitable for server-side rendering. So my next idea was to use isomorphic-style-loader. As far as I understand to get it working I need to decorate my component with their higher order component withStyles:
import React, { PropTypes } from 'react';
import classNames from 'classnames';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from '../assets/scss/common/index.scss';
const App = (props, context) => (
<div className={classNames('app')}>
<h1 className="home_header">Welcome!</h1>
{props.children}
</div>
);
export default withStyles(s)(App);
and then do some trickery in the code rendering page on the server. But the problem is, example from the package docs shows a flux action fired inside Express (https://libraries.io/npm/isomorphic-style-loader#webpack-configuration), and the boilerplate that I'm using uses react-router. So I'm kinda lost as how should I inject this object with insertCss into context. I tried this :
import React from 'react';
import { renderToString } from 'react-dom/server';
import { RouterContext, match, createMemoryHistory } from 'react-router';
import axios from 'axios';
import { Provider } from 'react-redux';
import createRoutes from 'routes.jsx';
import configureStore from 'store/configureStore';
import headconfig from 'components/Meta';
import { fetchComponentDataBeforeRender } from 'api/fetchComponentDataBeforeRender';
const clientConfig = {
host: process.env.HOSTNAME || 'localhost',
port: process.env.PORT || '3001'
};
// configure baseURL for axios requests (for serverside API calls)
axios.defaults.baseURL = `http://${clientConfig.host}:${clientConfig.port}`;
function renderFullPage(renderedContent, initialState, head = {
title: 'cs3',
css: ''
}) {
return `
<!DOCTYPE html>
<html lang="en">
<head>
${head.title}
${head.link}
<style type="text/css">${head.css.join('')}</style>
</head>
<body>
<div id="app">${renderedContent}</div>
<script type="text/javascript">window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};</script>
<script type="text/javascript" charset="utf-8" src="/assets/app.js"></script>
</body>
</html>
`;
}
export default function render(req, res) {
const history = createMemoryHistory();
const store = configureStore({
project: {}
}, history);
const routes = createRoutes(store);
match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
const css = [];
if (error) {
res.status(500).send(error.message);
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search);
} else if (renderProps) {
const context = { insertCss: (styles) => css.push(styles._getCss()) };
const InitialView = (
<Provider context={context} store={store}>
<RouterContext {...renderProps} />
</Provider>
);
fetchComponentDataBeforeRender(store.dispatch, renderProps.components, renderProps.params)
.then(() => {
const componentHTML = renderToString(InitialView);
const initialState = store.getState();
res.status(200).end(renderFullPage(componentHTML, initialState, {
title: 'foo',
css
}));
})
.catch(() => {
res.end(renderFullPage('', {}));
});
} else {
res.status(404).send('Not Found');
}
});
}
but I'm still getting Warning: Failed context type: Required context 'insertCss' was not specified in 'WithStyles(App)'. Any ideas how to tackle this ? And more importantly - is there no easier way to do it ? This seems like a lot of additional work.

There's a few parts to handling scss compilation with webpack when you're doing server-side rendering. First of all, you don't want node trying to import .scss files into your components.
So set a global variable WEBPACK: true in your webpack config:
plugins: [
new webpack.DefinePlugin({
'process.env': {
WEBPACK: JSON.stringify(true),
}
})
],
And in your components, only attempt to import .scss files if the component is being handled by webpack (either during build or development):
if (process.env.WEBPACK) require('../assets/scss/common/index.scss');
If you only have one Sass file per component (you should) then this is always just a one-liner. Any additional Sass files can be imported inside index.scss if you need to.
Then in your config you probably still want the css loader, so it should look like this for your dev server:
{
test: /\.s?css$/,
loaders: ['style', 'css', 'sass']
},
And something like this for you build config:
{
test: /\.s?css$/,
loader: ExtractTextPlugin.extract('style', 'css!sass')
},

Related

Invariant Violation: Browser history needs a DOM

For the server side, the BrowserRouter will not work and thus StaticRouter is to be used as per documentation. I am doing same but I am still getting the error. Following is my setup
Invariant Violation: Browser history needs a DOM
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter as Router } from 'react-router';
// import our main App component
import App from '../src/App';
const path = require('path');
const fs = require('fs');
export default (req, res) => {
// get the html file created by CRA's build tool
console.log('url : ', req.baseUrl);
const filePath = path.resolve(__dirname, '..', '..', 'build', 'index.html');
fs.readFile(filePath, 'utf8', (err, htmlData) => {
if (err) {
console.error('err', err);
return res.status(404).end();
}
// render the app as a string
const html = ReactDOMServer
.renderToString(<Router location={req.baseUrl}>
<App />
</Router>);
// now inject the rendered app into our html and send it
return res.send(htmlData
.replace('<div id="root"></div>', `<div id="root">${html}</div>`));
});
};

Warning: Prop `className` did not match. when using styled components with semantic-ui-react

I use this code to margin my Button from top:
const makeTopMargin = (elem) => {
return styled(elem)`
&& {
margin-top: 1em !important;
}
`;
}
const MarginButton = makeTopMargin(Button);
and whenever i use MarginButton node, I get this error: Warning: PropclassNamedid not match. Server: "ui icon left labeled button sc-bwzfXH MjXOI" Client: "ui icon left labeled button sc-bdVaJa fKCkqX"
You can see this produced here.
What should I do?
This warning was fixed for me by adding an .babelrc file in the project main folder, with the following content:
{
"presets": ["next/babel"],
"plugins": [["styled-components", { "ssr": true }]]
}
See following link for an example:
https://github.com/nblthree/nextjs-with-material-ui-and-styled-components/blob/master/.babelrc
Or you could just add this to your next.config.js. This also makes it so next-swc (speedy web compiler) works to reduce build times. See more here.
// next.config.js
module.exports = {
compiler: {
// Enables the styled-components SWC transform
styledComponents: true
}
}
You should install the babel plugin for styled-components and enable the plugin in your .babelrc
npm install --save-dev babel-plugin-styled-components
.babelrc
{
"plugins": [
[
"babel-plugin-styled-components"
]
]
}
The main reason I am posting this answer to help people understand the tradeoff. When we're using .babelrc in next project it's going to opt of SWC compiler which is based on Rust (Learn More).
It's going to show message something like this when you opt for custom bable config.
info - Disabled SWC as replacement for Babel because of custom Babel configuration ".babelrc"
I did more digging on this to only find out following! Ref
Next.js now uses Rust-based compiler SWC to compile
JavaScript/TypeScript. This new compiler is up to 17x faster than
Babel when compiling individual files and up to 5x faster Fast
Refresh.
So tradeoff was really huge, we can lose significant amout of performance. So I found a better solution which can solve this issue and keep SWC as default compiler.
You can add this experimental flag in your next.config.js to prevent this issue. Ref
// next.config.js
module.exports = {
compiler: {
// ssr and displayName are configured by default
styledComponents: true,
},
}
If you have already added babel plugins, delete the .next build folder & restart the server again
credit: Parth909 https://github.com/vercel/next.js/issues/7322#issuecomment-912415294
I was having the exact same issue and it was resolved by doing:
npm i babel-preset-next
npm install --save -D babel-plugin-styled-components
and adding this to .babelrc file:
{
"presets": ["next/babel"],
"plugins": [["styled-components", { "ssr": true }]]
}
Styled components server side rendering
Server Side Rendering styled-components supports concurrent server
side rendering, with stylesheet rehydration. The basic idea is that
everytime you render your app on the server, you can create a
ServerStyleSheet and add a provider to your React tree, that accepts
styles via a context API.
This doesn't interfere with global styles, such as keyframes or
createGlobalStyle and allows you to use styled-components with React
DOM's various SSR APIs.
import { renderToString } from 'react-dom/server'
import { ServerStyleSheet } from 'styled-components'
const sheet = new ServerStyleSheet()
try {
const html = renderToString(sheet.collectStyles(<YourApp />))
const styleTags = sheet.getStyleTags() // or sheet.getStyleElement();
} catch (error) {
// handle error
console.error(error)
} finally {
sheet.seal()
}
import { renderToString } from 'react-dom/server'
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
const sheet = new ServerStyleSheet()
try {
const html = renderToString(
<StyleSheetManager sheet={sheet.instance}>
<YourApp />
</StyleSheetManager>
)
const styleTags = sheet.getStyleTags() // or sheet.getStyleElement();
} catch (error) {
// handle error
console.error(error)
} finally {
sheet.seal()
}
In my case as im using nextjs
import Document, { Head, Main, NextScript } from "next/document";
import { ServerStyleSheet } from "styled-components";
export default class MyDocument extends Document {
static getInitialProps({ renderPage }) {
const sheet = new ServerStyleSheet();
const page = renderPage(App => props =>
sheet.collectStyles(<App {...props} />)
);
const styleTags = sheet.getStyleElement();
return { ...page, styleTags };
}
render() {
return (
<html>
<Head>{this.props.styleTags}</Head>
<body>
<Main />
<NextScript />
</body>
</html>
);
}
}
I have solved this issue following these steps.
Create a file named .babelrc in the root directory and configure the .babelrc file.
delete the .next build folder(It stores some caches).
Restart the server.
Hot reload the browser.
.babelrc configuration file
{
"presets": [
"next/babel"
],
"plugins": [
[
"styled-components",
{
"ssr": true,
"displayName": true,
"preprocess": false
}
]
]
}
PropType errors are runtime errors that will let you know that the data expected being passed to a prop is not what is expected. It looks like the className prop that is being set on your component is not the same when the component is rendered on the server and when it is then rendered in the client's DOM.
Since it looks like you are using server side rendering, you need to make sure that your class names are deterministic. That error is showing you the class that is being created by your styled-components library on the server and how it is different from the DOM. For libraries that do not normally have deterministic class names, you need to look at advanced configurations. Take a look at the styled-components documentation regarding specificity as it pertains to SSR.
//1. I got an error when using material-ui with Next.js
/********************************************* */
//2. The code I imported was like this :
const useStyles = makeStyles({
root: { // root must change
width: 100 ,
}
});
const Footer = () => {
const classes = useStyles();
return (
<div className={classes.root} > { /* root must change */}
<p> footer copyright #2021 </p>
</div>
)
}
export default Footer;
/********************************************* */
//3. I changed the code like this :
const useStyles = makeStyles({
footer: { // changed here to footer
width: "100%",
backgroundColor: "blue !important"
}
});
const Footer = () => {
const classes = useStyles();
return (
<div className={classes.footer} > { /* changed here to footer */}
<p> footer copyright #2021 </p>
</div>
)
}
export default Footer;
// I hope it works
For Old versions form Nextjs < 12, Go to next.config.js file and add this line inside nextConfig object:
experimental: {
// Enables the styled-components SWC transform
styledComponents: true
}
for new NextJs above 12:
compiler: {
styledComponents: true
}
if that does not work you need to make an NO SSR component wrapper like this:
// /components/NoSsr.js
import dynamic from 'next/dynamic'
const NoSsr = ({ children }) => <>{children}</>
export default dynamic(() => Promise.resolve(NoSsr), { ssr: false })
Then you need to add warp No SSR with your component like this:
// /pages/index.js
import NoSsr from '../components/NoSsr'
import CircleButton from '../components/buttons/CircleButton'
const HomePage = () => {
return (
<>
<p>Home Page Title</p>
<NoSsr>
{/* Here your styled-component */}
<makeTopMargin ele={...} />
</NoSsr>
</>
)
}
I'm using NextJS 12 and encountered the same issue, well error in the console, code was working ok.
I fixed it by creating a .babelrc file at the root of the project and add:
{
"presets": [
"next/babel"
],
"plugins": [
[
"styled-components",
{
"ssr": true,
"displayName": true,
"preprocess": false
}
]
]
}
Styled Components have full, core, non-experimental support in Next now (2022), but you have to turn them on:
Add the following to your next.config.js:
compiler: {
styledComponents: true,
},
My full, mostly vanilla, next.config.js now looks like this:
/** #type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
compiler: {
// Enables the styled-components SWC transform
styledComponents: true,
},
}
module.exports = nextConfig
https://nextjs.org/blog/next-12-1#improved-swc-support
I followed all the other advice, around setting up .babelrc (or .babelrc.js), but noticed this message in the Next.js docs:
When css-in-js libraries are not set up for pre-rendering (SSR/SSG) it will often lead to a hydration mismatch. In general this means the application has to follow the Next.js example for the library. For example if pages/_document is missing and the Babel plugin is not added.
That linked to this file, showing that I needed to add this to pages/_document.tsx to:
// if you're using TypeScript use this snippet:
import React from "react";
import Document, {DocumentContext, DocumentInitialProps} from "next/document";
import {ServerStyleSheet} from "styled-components";
export default class MyDocument extends Document {
static async getInitialProps(
ctx: DocumentContext,
): Promise<DocumentInitialProps> {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: App => props => sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}
}
A blog post by Raúl Sánchez also mentions this solution, linking to the JavaScript version if you're not using TS (pages/_document.js):
// if you're *not* using TypeScript use this snippet:
import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
})
const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
}
} finally {
sheet.seal()
}
}
}
If you are using create-react-app, you can use thi solution.
File called styled.ts
import styled from 'styled-components/macro';
import { css } from 'styled-components';
export const ListRow = styled.div`
...
...
`
Based on the files name, the prefix will be as following.
`${file_name}__{styled_component_name} ${unique_id}`
Meaning when implemented it will have the following classname
Although it would be nice to specify from where the first prefix would be taken from, meaning instead of file_name, we take folder_name. I currently dont know the solution for it.
To expand on C. Molindijk's answer, this error occurs when server-side class is different from client-side because styled-components generates its own unique class Id's. If your Next app is server-side rendered, then his answer is probably correct. However, Next.Js is by default statically generated, so unless you enabled SSR, configure it like this without ssr set to true:
{
"presets": ["next/babel"],
"plugins": [["styled-components"]]
}
This answer is for those who are using NextJs version > v12.0.1 and SWC compiler. You don't have to add _document.js file nor do babel related stuff anymore since it has been replaced by SWC compiler since v12.0.0. Only that your next.config.js file should look like the following since NextJs supports styled components after v12.1.0 and restart the server and it should work: more here
/** #type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
// add the following snippet
compiler: {
styledComponents: true,
},
};
module.exports = nextConfig;

Importing stylesheet in a server rendered react view - TypeError: style._getCss is not a function

I am building an isomorphic react-redux app on top of NodeJS. I am rendering my homePage from the server. However, my styles are not reflected in my rendered view. I would like to import stylesheets just how I do it on the client side. I tried this article and this too, but neither of them actually got me what I want to achieve.
Here are more details on the project.
.babelrc
{
"presets": [ "es2015", "react", "stage-0"],
"plugins": ["transform-decorators-legacy", ["transform-assets", {
"extensions": ["scss"],
"name": "[name].[ext]?[sha512:hash:base64:7]",
}]]
}
webpack.config.js
const path = require('path');
module.exports = [
{
name: 'client',
target: 'web',
entry: './routes/client.jsx',
output: {
path: path.join(__dirname, 'assets'),
filename: 'client.js',
publicPath: '/assets/',
},
resolve: {
extensions: ['.js', '.jsx']
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules\/)/,
use: [{ loader: 'babel-loader'}]
},
{
test: /\.scss$/,
use: [
{ loader: 'isomorphic-style-loader' },
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
localIdentName: '[name]__[local]___[hash:base64:5]',
sourceMap: true
}
},
{ loader: 'sass-loader'}
]
}
],
},
}];
server.js
import express from 'express'
import React from 'react'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import MainStore from './views/store/MainStore'
import { StaticRouter } from 'react-router-dom';
import Routes from './routes/routes';
import Template from './views/templates/template';
import { Helmet } from 'react-helmet';
import { renderToString } from 'react-dom/server'
import ReactDOMServer from 'react-dom/server';
import ContextProvider from './routes/contextProvider'
const webpackDevMiddleware = require('webpack-dev-middleware')
const config = require('./webpack/webpack.development.config.js')
const webpack = require('webpack')
const app = express()
const port = 3000
const compiler = webpack(config);
let preloadedState = { shipper: {view: "from_server"} }
app.use('/assets', express.static('./assets'))
app.use(webpackDevMiddleware(compiler, {
publicPath: "/assets/",
}));
app.use(handleRender);
function handleRender(req, res) {
// Create a new Redux store instance
const store = createStore(MainStore, preloadedState)
const css = new Set(); // CSS for all rendered React components
const context = { insertCss: (...styles) => styles.forEach(style =>
css.add(style._getCss())) }
const html = renderToString(
<Provider store={store}>
<StaticRouter context={context}>
<ContextProvider context={context}>
<Routes />
</ContextProvider>
</StaticRouter>
</Provider>
)
const finalState = store.getState()
const helmet = Helmet.renderStatic();
const preloadedState = store.getState()
res.send(renderFullPage(html, preloadedState));
}
function renderFullPage(html, finalState) {
return `
<!doctype html>
<html>
<head>
<title>Redux Universal Example</title>
<style type="text/css">${[...css].join('')}</style>
</head>
<body>
<div id="root">${html}</div>
<script>
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')}
</script>
<script src="./assets/client.js"></script>
</body>
</html>
`
}
app.listen(port)
contextProvider.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Routes from './routes.jsx';
export default class ContextProvider extends Component {
static childContextTypes = {
insertCss: PropTypes.func,
}
getChildContext() {
return { ...this.props.context }
}
render() {
const { children, ...props } = this.props
return React.cloneElement(children, props)
}
}
I am importing it in my homePresenter as
import homePageStyle from './home.scss';
and using it in my div in the same component as
<div className="component">
If I change this to
<div className={homePageStyle.component}>
I get an error on the browser
TypeError: style._getCss is not a function at server.js:52:84
On the browser, I could see that the div has class name as 'component'; it;'s just it is not inheriting the styles.
Any suggestions on what I am missing here?
TypeError: style._getCss is not a function at server.js:52:84
Most likely that server-side rendering is not configured properly. If you are not using webpack for compiling server-side bundle, of course, appropriate styles will not loaded, since isomorphic-style-loader had not processed files on server-side.
Check how application is started and ensure, that webpack had been applied for server-bundle and configuration was like this: https://webpack.js.org/configuration/node/
Also you can try ready isomorphic solution with frontend+backend bundling and includes css support, like resolve-app.

How to import the electron ipcRenderer in a react / webpack 2 setup

using a electron, react (es6 / jsx), sass, pouchdb and webpack 2 setup. I fail to import or require ipcRenderer to make communication between main and renderer process possible. My setup can be found here: https://github.com/wende60/timeTracker
Any hints how to get the ipcRenderer into a react component?
Cheers, jo
const electron = window.require('electron');
const ipcRenderer = electron.ipcRenderer;
I think it is the better solution because it avoid ejecting the React app.
I had the same problem. This solved that issue for me:
Add in the webpack.config.js:
const webpack = require("webpack");
module.exports = {
plugins: [
new webpack.ExternalsPlugin('commonjs', [
'electron'
])
]
...
}
Then you can use it with
import {ipcRenderer} from "electron";
I suggest you read my response here.
You'll want to set up your app like this:
main.js
const {
app,
BrowserWindow,
ipcMain
} = require("electron");
const path = require("path");
const fs = require("fs");
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;
async function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false, // is default value after Electron v5
contextIsolation: true, // protect against prototype pollution
enableRemoteModule: false, // turn off remote
preload: path.join(__dirname, "preload.js") // use a preload script
}
});
// Load app
win.loadFile(path.join(__dirname, "dist/index.html"));
// rest of code..
}
app.on("ready", createWindow);
ipcMain.on("toMain", (event, args) => {
fs.readFile("path/to/file", (error, data) => {
// Do something with file contents
// Send result back to renderer process
win.webContents.send("fromMain", responseObj);
});
});
preload.js
** Update: DO NOT use send key value as property name. It will overwrite on win.webContents.send method and comes to do nothing when you try to call win.webContents.send('your_channel_name') inside your main process main.js. Better to use better names like request and response.
const {
contextBridge,
ipcRenderer
} = require("electron");
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
"api", {
//send: (channel, data) => {
request: (channel, data) => {
// whitelist channels
let validChannels = ["toMain"];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
//receive: (channel, func) => {
response: (channel, func) => {
let validChannels = ["fromMain"];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
}
}
);
index.html
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8"/>
<title>Title</title>
</head>
<body>
<script>
window.api.response("fromMain", (data) => {
console.log(`Received ${data} from main process`);
});
window.api.request("toMain", "some data");
</script>
</body>
</html>
As of May 2020, I think Erik Martín Jordán has said it best:
Create a preload.js file:
window.ipcRenderer = require('electron').ipcRenderer;
On main.js:
// Create the browser window.
mainWindow = new BrowserWindow({
alwaysOnTop: true,
frame: false,
fullscreenable: false,
transparent: true,
titleBarStyle: 'customButtonsOnHover',
show: false,
width: 300,
height: 350,
webPreferences: {
// UPDATE: for electron > V12 consider setting contextIsolation and see: https://github.com/electron/electron/issues/9920#issuecomment-797491175
nodeIntegration: true,
preload: __dirname + '/preload.js'
}
});
// Blur window when close o loses focus
mainWindow.webContents.on('did-finish-load', () => mainWindow.webContents.send('ping', '🤘') );
mainWindow variable on this file will preload the preload.js file. Now the React component can call the window.ipcRenderer method.
In the React app.js:
import React, { useEffect, useState } from 'react';
import './App.css';
function App() {
useEffect( () => {
window.ipcRenderer.on('ping', (event, message) => {
console.log(message)
});
}, []);
return (
<div className = 'App'></div>
);
}
export default App;
I've been looking into this topic just recently and I found a solution for doing some some ipc between electrons main.js and the React part of the application. Since both, import {ipcRenderer} from 'electron'; after adding the plugin to the webpack modules and const ipc = require('electron').ipcRenderer; produced some errors I ended up requiring electron in the resulting page and adding it to the window.
In the index.html I did something like this
<body>
...
<script>
window.ipc = require('electron').ipcRenderer;
</script>
<div id="root">
</div>
...
</body>
In reacts index.js I did something like this:
import React from 'react';
import ReactDOM from 'react-dom';
// ...
if(window.ipc)
ipc.on("some-event", (event, someParameter) => {
ReactDOM.render(
<SomeElement value={someParameter} />,
document.getElementById("root")
);
})
// ...
In order to make this work i started the react page from the electron app, in the main.js I did something like that.
const {app, BrowserWindow} = require("electron"};
const exec = require("child_process").exec;
let main;
app.on("ready", () => {
exec("node start", (err, stdout, stderr) => {
if(err) console.log(err);
console.log("" + stdout);
console.log("" + stderr);
});
main = new BrowserWindow();
main.loadURL("http://localhost:3006");
main.on("close", () => {main = null});
});
because electron is running on the same port I added a .env file to my project that contained
PORT=3006
I used the create-react-app my-prj (npm install -g create-react-app) command for my base project it looked something like this
my-prj
|-package.json
|-main.js
|-.env
|-node_modules/
|-public/
|-index.html
|-src/
|-index.js
Hope this post was of any help.
Found a good solution for this issue using webpack-target-electron-renderer So I can develop the web-part in a localhost environment with hot-reloading. electron is required only in the electron environment.
You can see a working example here:
https://github.com/wende60/webpack-web-and-electron-example, forked from acao's webpack-web-and-electron-example and updated for webpack 2 and hot-replacement.
If you are interested in a webpack, electron, react, sass and pouchdb setup have a look here:
https://github.com/wende60/timeTracker
Work is still in progress...
import { ipcRenderer } from "electron"
import { Component } from "react"
...
class MyComponent extends Component {
render(){
ipcRenderer.send("event", "some data")
return (<div>Some JSX</div>)
}
}

Proper way to implement jwplayer in react component using webpack (react-starter-kit)

i am making VideoPlayer react component with jwpalyer and i am using webpack es6 for loading module
webpack support npm module loading & there is no npm for jwplayer
so am trying to include jwplayer.js using es6 import but it giving me error
ReferenceError: window is not defined
so any one can help me to properly setup jwplayer with webpack
import React, { PropTypes, Component } from 'react';
import $ from 'jquery';
import Player from "./lib/jwplayer/jwplayer.js";
import styles from './VideoPayer.css';
import withStyles from '../../decorators/withStyles';
import Link from '../Link';
#withStyles(styles)
class VideoPlayer extends Component {
static propTypes = {
className: PropTypes.string,
};
static defaultProps = {
file: '',
image: ''
};
constructor(props) {
super(props);
this.playerElement = document.getElementById('my-player');
}
componentDidMount() {
if(this.props.file) {
this.setupPlayer();
}
}
componentDidUpdate() {
if(this.props.file) {
this.setupPlayer();
}
}
componentWillUnmount() {
Player().remove(this.playerElement);
}
setupPlayer() {
if(Player(this.playerElement)) {
Player(this.playerElement).remove();
}
Player(this.playerElement).setup({
flashplayer: require('./lib/player/jwplayer.flash.swf'),
file: this.props.file,
image: this.props.image,
width: '100%',
height: '100%',
});
}
render() {
return (
<div>
<div id="my-player" className="video-player"></div>
</div>
)
}
}
export default VideoPlayer;
I think this is what you need to do:
Define window as external to the bundle so that references to it in other libraries are not mangled.
Expose a global variable jwplayer so that you can attach your key
(Optional) Create an alias to your jwplayer library
I've tested it and this configuration works for me, but only on the client and not on the server or isomorphically/universally.
webpack.config.js:
// Declare window as external
externals: {
'window': 'Window'
},
// Create an easy binding so we can just import or require 'jwplayer'
resolve: {
alias: {
'jwplayer':'../path/to/jwplayer.js'
}
},
// Expose jwplayer as a global variable so we can attach the key, etc.
module: {
loaders: [
{ test: /jwplayer.js$/, loader: 'expose?jwplayer' }
]
}
Then you can import jwplayer from 'jwplayer' and require('jwplayer').
Probably an old question but I recently found a relatively stable solution.
I include the jwplayer in a folder called app/thirdparty/jwplayer-7.7.4. Next, add it to the exclude in the babel loader so it is not parsed.
{
test: /\.jsx?$/,
use: 'babel-loader',
exclude: /(node_modules|thirdparty)/,
}
I then use dynamic import in order to bootstrap my component and load jwplayer.
async function bootstrap(Component: React.Element<*>) {
const target = document.getElementById('root');
const { render } = await import('react-dom');
render(<Component />, target);
}
Promise.all([
import('app/components/Root'),
import('app/thirdparty/jwplayer-7.7.4/jwplayer.js'),
]).then(([ { default: Root } ]) => {
window.jwplayer.key = "<your key>";
bootstrap(Root);
});

Resources