ReferenceError: document is not defined (React SSR, Webpack) - node.js

I'm trying to do server-side rendering my react app but it doesn't work. It shows document not define when trying to run. This error only shows when I used CSS file in app.js file. Though client-side render working properly.
//My App.js
import React from 'react';
import { renderRoutes } from 'react-router-config';
import ErrorBoundary from './validation/ErrorBoundry';
import './assets/css/bootstrap.min.css';
import './assets/scss/main.css';
const App = ({ route }) => {
return (
<div className="App">
<ErrorBoundary>{renderRoutes(route.routes)}</ErrorBoundary>
</div>
);
};
export default App;
//webpack.base.js
module.exports = {
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
],
}
]
},
}
error screenshot

The document object is the root node of the HTML document.
This object does not exist in node application.
And the way to handle it in node application can be done by using jsdom.
jsdom is a pure-JavaScript implementation of many web standards,
notably the WHATWG DOM and HTML Standards, for use with Node.js
That mistake happens because, Webpack transforms the CSS to the following code:
var css_main_css = document.createElement("style");
css_main_css.innerHTML = "*css*";
document.querySelector("head").appendChild(css_main_css);

Related

How to handle ejs views using webpack

I'm trying to configure webpack with my website using node js, I'm using also ejs as a view. I have tried with many ways to handle the ejs in my webpack, but till now I didn't get success.
const path = require('path')
const nodeExternals = require('webpack-node-externals')
module.exports = (env, argv) => {
return ({
entry: {
server: './src/app.js',
},
output: {
path: path.join(__dirname, 'dist'),
publicPath: '/',
filename: 'main.js'
},
mode: argv.mode,
target: 'node',
node: {
// Need this when working with express, otherwise the build fails
__dirname: true,
__filename: true,
},
externals: [nodeExternals()], // Need this to avoid error when working with Express
module: {
rules: [
{
// Transpiles ES6-8 into ES5
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.ejs$/,
loader: 'ejs-loader',
options: {
esModule: false
}
}
]
}
})
}
when I use HtmlWebPackPlugin I get some errors because of data inside <%- %> it's like he didn't know this data where comes from. like for example, <%- include('partials/head.ejs') %>.
is there a way to handle my views as ejs using webpack?
I know this has been asked a few months ago. But for those who come across this issue like I have, this is how I got this to work. Assuming your using webpack 4.
If you have not already install html-webpack-plugin
Most importantly to help solve the issue install raw-loader
add the following to your webpack config
new HtmlWebpackPlugin({
template: '!!raw-loader!./src/views/pages/<file-name-here>.ejs',
filename: 'index.ejs',
chunks: ['main', 'owl_carousel']
})
This is where the magic is. when including the template path make sure to include !!raw-loader! followed by the relative path.
raw-loader makes it so when html plugin creates the file it ignores the special syntax ejs uses. It is basically like "hey plugin ignore whatever I put here and just get me my file".
As #JRichardsz explained, You won't need Webpack explicitly to use EJS templates in your NodeJS project.
Also, It simply bundles up the EJS template (code) implicitly.
Try to bundle up your files with latest Webpack.js using below command to install:
npm install --save-dev webpack
Also, try this code with a little fix:
module.exports = (env, argv) => {
return ({
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
filename: 'main.js'
}
...
// in case, all of this doesn't work. Then, explicitly whitelist EJS using regex
...
nodeExternals({
whitelist: [/\.(?|ejs)$)],
}),
...
})
}
If you want to use ejs for nodejs projects, you don't need webpack. Webpack is commonly used for client side rendering like angular, vue, react, etc
Check this: Which command should I use to minify and optimize nodejs express application? to view some ways to optimize your static js files used in ejs or another nodejs server rendering framework.
Basic structure for ejs projects is:
|____
|____server.js
|____views
| |____hello.ejs
|____package.json
hello.ejs a simple and plain template in which you can use any of ejs code like your
<%- include('partials/head.ejs') %>
As you can see, you don't need webpack to run ejs apps.
Check these samples:
minimal ejs sample
partials sample
You would need to make bundle of the EJS.
Try below commands:
module.exports = (env, argv) => {
return ({
output: {
path: './dist',
publicPath: '/',
filename: 'main.js'
}
})
}
copy-webpack-plugin worked perfectly
plugins: [
new webpack.ProgressPlugin(),
new CopyWebpackPlugin({
patterns: [
{from: "src/views", to: "views"}
]
})

I am trying to use react-app-rewired for changing file-loader config

I'm using react-app-rewired to change the webpack config. My intention is to be able to load GLTF files into react for threejs.
I found an example using the npm package. https://www.npmjs.com/package/gltf-webpack-loader. I have tried the following code but I getting an error with my webpack. The following code is my config-overrides.js
module.exports = function override(config, env) {
config.module.rules.push({
test: /\.(gltf)$/,
use: [
{
loader: "gltf-webpack-loader"
}
]
});
config = { ...config, {
test: /\.(bin)$/,
use: [
{
loader: "file-loader",
options: {}
}
]
}
};
return config;
};
I want to be able to load GLTF files into a react application.

How to include Mongoose/MongoDB in an Express.JS-App (the ES2015 way)

I am trying to include mongoose/mongodb in my Express.JS app (the ES2015 way).
First, I have the main file:
import path from 'path'
import express from 'express'
import mongoose from 'mongoose'
mongoose.connect('mongodb://localhost/timelines')
const app = express()
app.use(express.static('dist/client/'))
app.get('*', (req, res) => {
res.sendFile(path.resolve('dist/client/index.html'))
})
app.listen(3000)
Then the error message:
[nodemon] starting `node dist/server/app.js`
undefined:8
driver = !(function webpackMissingModule() { var e = new Error("Cannot find module \".\""); e.code = 'MODULE_NOT_FOUND';; throw e; }());
^
Error: Cannot find module "."
My webpack config:
export default [
{
entry: './src/server/app.js',
target: 'node',
output: {
path: './dist/server',
filename: 'app.js'
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel-loader',
query: {
presets: ['es2015']
}
}
]
}
},
{
entry: './src/client/app.js',
output: {
path: './dist/client',
filename: 'app.js'
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel-loader',
query: {
presets: ['es2015', 'stage-2', 'react']
}
}
]
}
}
]
Now, the interesting part:
Before I added
import mongoose from 'mongoose'
mongoose.connect('mongodb://localhost/timelines')
everything worked fine!!
Any thoughts how this problem can be fixed?
EDIT 1:
webpack throws a warning:
WARNING in ./~/es6-promise/dist/es6-promise.js
Module not found: Error: Can't resolve 'vertx' in '/Users/timo/Desktop/Timelines/node_modules/es6-promise/dist'
# ./~/es6-promise/dist/es6-promise.js 131:20-30
# ./~/mongodb/lib/mongo_client.js
# ./~/mongodb/index.js
# ./~/mongoose/lib/index.js
# ./~/mongoose/index.js
# ./src/server/app.js
I ran into the same issue, and there were a few key steps in order to resolve it.
Firstly, please check that you have the version of MongoDB server installed that Mongoose is expecting:
Mongoose MongoDB NodeJS driver compatibility
The root cause of the issue is that Webpack is trying to resolve a dynamic require to load the MongoDB NodeJS driver:
if (typeof window === 'undefined') {
driver = require(global.MONGOOSE_DRIVER_PATH || './node-mongodb-native');
} else {
driver = require('./browser');
}
We need to instruct webpack to ignore all external imports as we don't need to bundle them, since they will never be sent down to the client.
We can do that by adding the following webpack config:
module.exports = {
// tell webpack that it is building for the NodeJS environment
...
target: 'node',
output: {
...
// we want the output to use simple require calls for imports as
// nodejs would expect
libraryTarget: 'commonjs'
},
...
externals: [
// consider everything imported from a non-relative path an external
// this will flag everything such as:
// import express from 'express'
// or
// import mongoose from 'mongoose'
/^(?!\.|\/).+/i
]
}
When you run your build again and start up the resultant bundle, you shouldn't receive the cryptic error message from Webpack any longer and find that your server is now running as expected.
I think you're missing a semicolon. Just kidding. (But seriously, if you're not using semicolons then you will experience problems if you're not 100% sure what you're doing. At least lint your code according to eslint-config-npm to make sure that you're not missing a semicolon where it actually matters.)
Make sure that you installed the mongoose module correctly with its dependencies and that you include everything needed in webpack. It looks like some code is trying to import '.' and cannot do it - for example there's no package.json or incomplete package.json there.

How to structure a Vue 2.0 app for server-rendered lazy routes?

I attempted to modify vue-hackernews-2.0 to support lazy-loaded routes using Webpack's code-splitting feature as per instructions found at these links:
https://router.vuejs.org/en/advanced/lazy-loading.html
http://vuejs.org/guide/components.html#Async-Components
However, I ran into some issues. When I loaded the app in the browser, all suggested variations of syntax triggered Module not found errors on the server-side when attempting to load in the server-side chunks.
Given this wrapper around the code-split points in router.js...
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
// INSERT CODE-SPLIT POINT SYNTAXES HERE (they are below)
export default new Router({
mode: 'history',
routes: [{
path: '/',
component: Home
}, {
path: '/foo',
component: Foo
}]
})
All of these variations of syntax threw the Module not found error:
Variation 1:
const Home = () => System.import('./views/Home.vue')
const Foo = () => System.import('./views/Foo.vue')
Variation 2:
const Home = (resolve) => require(['./views/Home.vue'], resolve)
const Foo = (resolve) => require(['./views/Foo.vue'], resolve)
Variation 3:
const Home = (resolve) => {
require.ensure(['./views/Home.vue'], () => {
resolve(require('./views/Home.vue'))
})
}
const Foo = (resolve) => {
require.ensure(['./views/Foo.vue'], () => {
resolve(require('./views/Foo.vue'))
})
}
The error message was always along the lines of:
(note: this error is adapted from a small reproduction I made of the issue, not from the hackernews example)
Error: Cannot find module './0.server.js'
at Function.Module._resolveFilename (module.js:440:15)
at Function.Module._load (module.js:388:25)
at Module.require (module.js:468:17)
at require (internal/module.js:20:19)
at Function.requireEnsure [as e] (__vue_ssr_bundle__:42:25)
at Home (__vue_ssr_bundle__:152:30)
at /Users/razorbeard/projects/vue-2-ssr/node_modules/vue-router/dist/vue-router.js:1421:19
at iterator (/Users/razorbeard/projects/vue-2-ssr/node_modules/vue-router/dist/vue-router.js:1277:5)
at step (/Users/razorbeard/projects/vue-2-ssr/node_modules/vue-router/dist/vue-router.js:1213:9)
at step (/Users/razorbeard/projects/vue-2-ssr/node_modules/vue-router/dist/vue-router.js:1217:9)
I tried adapting my code to use the suggestions offered at Server-side react with webpack 2 System.import, but those did not work either.
I read a post that described configuring a build-time global variable using Webpack's DefinePlugin that allowed me to inspect whether the code was running on the server or on the client - this allows me to code-split on the client, but just bundle everything in on the server.
In the server webpack config:
{
...
plugins: [
new webpack.DefinePlugin({
BROWSER_BUILD: false
})
]
...
}
In the client webpack config:
{
...
plugins: [
new webpack.DefinePlugin({
BROWSER_BUILD: true
})
]
...
}
Then, in the same wrapper-snippet as above for the router.js file, I used this variation of syntax:
const Home = BROWSER_BUILD ? () => System.import('./views/Home.vue') : require('./views/Home.vue')
const Foo = BROWSER_BUILD ? () => System.import('./views/Foo.vue') : require('./views/Foo.vue')
This made rendering work - partially. Navigating directly to the app in the browser (and respective routes) server-rendered the correct UI. Clicking around, vue-router's client-side logic took me to the right UI. Everything seemed hunky-dory - until I opened DevTools:
The same issue also occurs if a module is loaded lazily as a subcomponent of a route:
<template>
<div class="page">
<heading></heading>
</div>
</template>
<script>
const Heading = BROWSER_BUILD ? () => System.import('./Heading.vue') : require('./Heading.vue')
export default {
components: {
Heading
}
}
</script>
I tried asking for some help in the official Vue forum, but came up empty: http://forum.vuejs.org/t/2-0-help-needed-with-server-rendered-lazy-routes/906
Thinking this might be a bug with vue-router, I opened an issue there: https://github.com/vuejs/vue-router/issues/820
Unfortunately, I wasn't able to find a solution.
So, I put together a small repo that reproduces the issue: https://github.com/declandewet/vue2-ssr-lazy-error
I have a hunch that the actual problem might be coming from https://www.npmjs.com/package/vue-server-renderer.
I'm really stuck on this and am used to how easy it is to do in react - and would really appreciate any help/tips/direction towards a solution!
Here is the webpack config from the reproduction repo for convenience:
import fs from 'fs'
import path from 'path'
import webpack from 'webpack'
import validate from 'webpack2-validator'
import { dependencies } from './package.json'
let babelConfig = JSON.parse(fs.readFileSync('./.babelrc'))
/* turn off modules in es2015 preset to enable tree-shaking
(this is on in babelrc because setting it otherwise causes issues with
this config file) */
babelConfig.presets = babelConfig.presets.map(
(preset) => preset === 'es2015' ? ['es2015', { modules: false }] : preset
)
const babelOpts = {
...babelConfig,
babelrc: false,
cacheDirectory: 'babel_cache'
}
const SHARED_CONFIG = {
devtool: 'source-map',
module: {
loaders: [{
test: /\.vue$/,
loader: 'vue'
}, {
test: /\.js$/,
loader: 'babel',
exclude: 'node_modules',
query: babelOpts
}]
},
resolve: {
modules: [
path.join(__dirname, './src'),
'node_modules'
]
}
}
const SERVER_CONFIG = validate({
...SHARED_CONFIG,
target: 'node',
entry: {
server: './src/server.js',
renderer: './src/renderer.js'
},
output: {
path: path.join(__dirname, './dist'),
filename: '[name].js',
chunkFilename: '[id].server.js',
libraryTarget: 'commonjs2'
},
plugins: [
new webpack.DefinePlugin({
BROWSER_BUILD: false,
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
}),
new webpack.BannerPlugin({
banner: 'require("source-map-support").install();',
raw: true,
entryOnly: false
})
],
externals: Object.keys(dependencies)
})
const CLIENT_CONFIG = validate({
...SHARED_CONFIG,
entry: {
app: './src/client.js',
vendor: ['vue']
},
output: {
path: path.join(__dirname, './dist/assets'),
publicPath: '/',
filename: 'bundle.js'
},
plugins: [
new webpack.DefinePlugin({
BROWSER_BUILD: true,
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'vendor.js'
})
]
})
export default [SERVER_CONFIG, CLIENT_CONFIG]
EDIT: Noticing that in React, we use match on the client to get the right route config for the current view, I decided to inspect what components were getting matched using app.$router.getMatchedComponents() and found something interesting:
Server Entry:
import app from './app'
export default (context) => {
// using app.$router instead of importing router itself works
// (not sure why the hacker-news example imports the router module instead...)
app.$router.push(context.url)
const components = app.$router.getMatchedComponents()
console.log('server-side', components)
return Promise.all(components.map((component) => component))
.then(() => app)
}
When navigating to the home page, this logs to the terminal:
server-side [ { __file: '/Users/razorbeard/projects/vue-2-ssr/src/views/Home.vue',
render: [Function],
staticRenderFns: [ [Function] ] } ]
Client Entry:
import app from './app'
const components = app.$router.getMatchedComponents()
console.log('client-side', components)
// kickoff client-side hydration
Promise.all(components.map((component) => Promise.resolve(component)))
.then(() => app.$mount('#app'))
When navigating to the home page, this logs to the devtools console:
client-side []
As you can see, no components are getting matched on the client side.

Node, Webpack and React Routing using FS filesystem

I want to read and write a file using NodeJS default fs module.
I've almost tried everything I can find on the internet. ES6 and CommonJS examples, editing the Webpack.config file, added packages that should add Promises and so on.. But nothing seems to work out.
Currently this is my webpack.config.js
var webpack = require('webpack');
module.exports = {
entry: './index.js',
output: {
path: 'public',
filename: 'bundle.js',
publicPath: ''
},
node: {
fs: "empty"
},
"browser": { "fs": false },
resolve: {
modulesDirectories: ['web_modules', 'bower_components', 'node_modules']
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader?presets[]=es2015&presets[]=react'
}
]
}
}
As I've mentioned some lines suggested in this topic where added.
I want to use the fs.readFile in the component shown below:
var React = require('react');
var fs = require('fs');
var QuestionList = React.createClass({
handleVote: function(id, state) {
var file = '../public/api/questions';
fs.readSyncFile(file, function(err, data) {
console.log( err );
console.log( data );
});
},
render() {
var rows = []
this.state.questions.forEach( function(question) {
rows.push(
<QuestionItem
onVoteUpdate={this.handleVote}
key={question.id}
up={question.upvotes} down={question.downvotes}
/>
);
}.bind(this));
return (
<section>
<ul className="question-list">{rows}</ul>
</section>
)
}
});
module.exports = QuestionList;
I've removed some functions, like loading the question with jQuery and set the InitialState, for this example.
I'll can imagine webpack can't build any back-end tasks like FS in an front-end js file or something like that, but how is it possible to read and write files using Node, Webpack and React? Is this even possible?
You can't use fs in a front-end JS file, as far as I know, as you don't have access to the filesystem from the browser (it looks like you might be expecting the readFileSync call to get run during Webpack's compilation - this isn't how it works!). You either need a Webpack loader of some kind to make it so you can require() the file, or you'll have to load it via AJAX.

Resources