reactjs / react-chartjs

common react charting components using chart.js
MIT License
2.93k stars 299 forks source link

server side rendering, getting window is not defined error #57

Closed omerts closed 8 years ago

omerts commented 8 years ago

Hello,

Trying to use this library with server-side rendering, but I am getting a "window is not defined error", since Chart.js is referencing the browser's window object, even if it is only imported, before any usage.

joshhornby commented 8 years ago

Please see this https://github.com/kirjs/react-highcharts/issues/12#issuecomment-122085876. Although its for Highcharts the same logic applies.

enten commented 8 years ago

Related issue: chartjs/Chart.js#1328

Below is an another fix for those who want to develop an isomorphic react app.

In my package.json:

"scripts": {
  // ...
  "postinstall": "npm i find-up prepend-file && node scripts/postinstall.js",
  // ...
},
"dependencies": {
  "chart.js": "^2.1.6",
  // ...
  "react-chartjs": "github:jhudson8/react-chartjs#chartjs-v2",
  // ...
},
"devDependencies": {
  // ...
  "prepend-file": "^1.3.0",
  // ...
}

In my webpack config (used to generate the client side code):

plugins: [
  // ...
  new webpack.DefinePlugin({
    'process.env.BROWSER': JSON.stringify(true)
  }),
  // ...
]

And the postinstall.js script:

#!/usr/bin/env node

var path = require('path');
var prepend = require('prepend-file');
var findUp = require('find-up')

var FIXED_FILE = ['chart.js', 'src', 'core', 'core.helpers.js'];
var FIXED_CODE = '// < HACK >\n'
  +'if (!process.env.BROWSER) {\n'
  +'  global.window = {};\n'
  +'}\n// </ HACK >\n\n';

function hackChartJs() {
  findUp('node_modules')
    .then(nodeModules => prepend(
      path.resolve.apply(path, [nodeModules].concat(FIXED_FILE)),
      FIXED_CODE,
      console.log
    ));
}

hackChartJs();

So when I install my app, the postinstall.js script will prepend the file node_modules/chart.js/src/core/core.helpers.js with the codefix below:

// < HACK >
if (!process.env.BROWSER) {
  global.window = {};
}
// </ HACK >

That is ugly but it works.

Update 11 july 2016:

dcurletti commented 7 years ago

Another way to solve this would be to use code splitting. https://webpack.github.io/docs/code-splitting.html#defining-a-split-point

If you dynamically require Chart.js inside of ComponentDidMount and then initialize the chart in the callback, you can avoid a lot of nasty workarounds.

mtford90 commented 7 years ago

I added the following to core.helpers.js:

if (typeof window === 'undefined') {
    global.window = {}
}

Works a treat. Can use the same method as @enten uses above to make sure this is always prepending to core.helpers.js on install.

pagesrichie commented 7 years ago

@enten Good fix - it works for those of us that aren't using webpack, and it worked for my Meteor based solution.

However - with your code every time we run an 'npm install' it's going to write multiple copies of the javascript code we want to prepend.

So I came up with an add-on to your solution and mixed up @mtford90 's code. This takes a regular expression and searches for the // < HACK > till the end of the // < /HACK > and deletes it if it exists, before prepending the new one. After I wrote this, I realized the most optimized approach would be to actually search to see if it exists, then prepend the code otherwise don't.. but here goes :) !

Packages installed to dev-dependencies for this code: path, prepend-file, find-up and replace-in-file.

`var path = require('path'); var prepend = require('prepend-file'); var findUp = require('find-up'); var replace = require('replace-in-file');

var FIXED_FILE = ['chart.js', 'src', 'core', 'core.helpers.js'];
var FIXED_CODE = '// < HACK >\n'
+'if (typeof window === \'undefined\') {\n'
+'  global.window = {};\n'
+'}\n// </ HACK >\n\n';

function hackChartJs() {
    findUp('node_modules')
    .then(nodeModules => {
        var completeFilePath = path.resolve.apply(path, [nodeModules].concat(FIXED_FILE))

        var replace_options = {
            files: completeFilePath,
            from: /\/\/ < HACK >[\s\S]*?\/\/ <\/ HACK >[\s]*/g,
            to: '',
        };

        try {
            var changes = replace.sync(replace_options);
            console.log('Modified files:', changes.join(', '));
        }
        catch (error) {
            console.error('Error occurred:', error);
        }

        prepend(
            completeFilePath,
            FIXED_CODE,
            console.log
        );
    });
}

hackChartJs();`

Works like a charm and now my meteor + react-router-ssr (Server Side Rendering for React) project works beautifully!