Node, Webpack and React Routing using FS filesystem - node.js

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.

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"}
]
})

How do I integrate NewRelic into a Node Typescript Express server bundled with Webpack?

Frankly, I've tried it all. I'm not a total whiz with Webpack, however I seem to be getting along pretty well over the years with configuring new projects.
What I cannot seem to do now is set up the NewRelic service into an existing Node/Typescript/Express/Webpack application.
As it stands, my app gets nicely bundled to a single file in my /dist folder and runs quick and nimble. Seems like this 'node agent' put out by New Relic doesn't play well with Typescript imports.
Webpack Config
const path = require('path');
const webpack = require('webpack');
const nodeExternals = require('webpack-node-externals');
const NodemonPlugin = require ('nodemon-webpack-plugin');
module.exports = (env = {}) => {
const config = {
entry: ['./src/app.ts'],
mode: env.development ? 'development' : 'production',
target: 'node',
devtool: env.development ? 'inline-source-map' : false,
resolve: {
extensions: ['.ts', '.js'],
modules: ['node_modules', 'src', 'package.json'],
},
module: {
rules: [
{
test: /\.ts$/,
use: ['ts-loader', 'eslint-loader'],
// exclude: /node_modules/,
},
],
},
plugins: [],
externals: [ 'newrelic', nodeExternals() ]
};
if (env.nodemon) {
config.watch = true;
config.plugins.push(new NodemonPlugin())
}
return config;
};
there exists a standard /project_root/.newrelic file
CircleCi picks up this project up and runs "build:ci" script from package.json ==> "webpack"
output is /dist/main.js
references
https://docs.newrelic.com/docs/agents/nodejs-agent/installation-configuration/install-nodejs-agent
https://docs.newrelic.com/docs/agents/nodejs-agent/installation-configuration/nodejs-agent-configuration
https://discuss.newrelic.com/t/node-agent-fails-with-webpack/24874
Your first line of the starting point of the app should be
import newrelic from 'newrelic';
Of course, run npm install newrelic --save first
Then, create a newrelic.js file on the root of the repo (outside of src).
Then you put in the details like:
'use strict'
exports.config = {
app_name: ['appName'],
license_key: '1234567890',
allow_all_headers: true,
attributes: {
exclude: [
'request.headers.cookie',
'request.headers.authorization',
'request.headers.proxyAuthorization',
'request.headers.setCookie*',
'request.headers.x*',
'response.headers.cookie',
'response.headers.authorization',
'response.headers.proxyAuthorization',
'response.headers.setCookie*',
'response.headers.x*'
]
}
}

Webpack not creating the HTML file it used to create

I was building a nodejs app (typescript, react, webpack) and the build was working fine. I.e. I got in the output what I expected. A HTML file, with the bundle file I expected.
Suddenly, without any change in my code, webpack is only generating a the bundle javascript file (as usual) but the HTML file is gone, i.e it does not generate any more.
Here my webpack config file:
const path = require('path');
const config = {
mode: "production",
entry: path.resolve(__dirname, "../src/index.tsx"),
resolve: {
extensions: ['.tsx', '.js']
},
output: {
// options related to how webpack emits results
path: path.resolve(__dirname, "../dist"),
filename: "bundle.js"
},
module: {
rules: [
{
test: /.tsx$|.js$/,
loader: "ts-loader" ,
include: path.resolve(__dirname, "../src")
},
]
}
}
module.exports = config;
Any clue why my HTML file is not generated anymore and how I can recover it? Thank you!
As I know Webpack by default outputs main.js if you do not specify output filename configuration like this code. Try this code:
const path = require('path');
const config = {
entry: path.resolve(__dirname, "../src/index.tsx"),
resolve: {
extensions: ['.tsx', '.js']
},
output: {
// options related to how webpack emits results
path: path.resolve(__dirname, "../dist"),
filename: 'my-first-webpack.bundle.js'
},
module: {
rules: [
{
test: /\.tsx$/,
loader: "ts-loader" ,
include: path.resolve(__dirname, "../src")
},
]
}
}
module.exports = config;
So the HTML was actually never created. So the question is obsolete.
I was confused because before there used to be an HTML in the output. But it was not generated by webpack (it came from Typescript script)

Accessing process.env.PORT defined in Elastic Beanstalk Node, bundle built by Webpack

Currently I'm building out a REACT app on my local machine and deploying it's production build to Elastic Beanstalk. It is built using Webpack.
In index.js (server side) I use env variables such as: process.env.PORT (Defined in Elastic Beanstalk), when Webpack builds it out, these are replaced with Objects({}) containing local process.env.
Is there a way to prevent Webpack from evaluating certain env variables?
or am I going about it wrong and do I need to build the production bundle on Elastic Beanstalk first and then serve?
Worst case I can simply add the required values to a dotenv file and approach it that way. I would prefer to be able to make use of Elastic Beanstalks Enviroment Variables though.
Thanks in advance
Update:
To better explain below, I'm trying to access process.env variables during runtime in the server side script. The code is however built on my local machine before being deployed to AWS Elastic Beanstalk
webpack.config.server
'use strict';
const util = require('util');
const path = require('path');
const webpack = require('webpack');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
// const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const paths = require('./paths');
const nodeExternals = require('webpack-node-externals');
const getClientEnvironment = require('./env');
const merge = require('webpack-merge');
const base = require('./webpack.config.base');
const publicUrl = '';
// Get environment variables to inject into our app.
const env = getClientEnvironment(publicUrl);
const config = {
target: 'node',
entry: paths.serverIndexJs,
externals: [nodeExternals()], // / in order to ignore all modules in node_modules folder
output: {
path: paths.serverBuild,
filename: 'bundle.js',
publicPath: '/'
},
module: {
rules: [
// "postcss" loader applies autoprefixer to our CSS.
// "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader turns CSS into JS modules that inject <style> tags.
// In production, we use a plugin to extract that CSS to a file, but
// in development "style" loader enables hot editing of CSS.
{
test: /\.scss$/,
include: [ path.resolve(paths.scss, 'vendor') ], // Global styles
use: [
{
loader: "isomorphic-style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader", // translates CSS into CommonJS
options: {
importLoaders: 1,
sourceMap: true
}
}, {
loader: "postcss-loader",
options: {
sourceMap: true
}
}, {
loader: "sass-loader", // compiles Sass to CSS
options: {
sourceMap: true
}
}
]
},
{
test: /\.scss$/,
exclude: [ path.resolve(paths.scss, 'vendor'), path.resolve(__dirname, '../node_modules') ], // Module styles
use: [
{
loader: "isomorphic-style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader", // translates CSS into CommonJS
options: {
importLoaders: 1,
sourceMap: true,
modules: true,
localIdentName: '[path]___[name]__[local]___[hash:base64:5]'
}
}, {
loader: "postcss-loader",
options: {
sourceMap: true
}
}, {
loader: "sass-loader", // compiles Sass to CSS
options: {
sourceMap: true
}
}, {
loader: "sass-resources-loader",
options: {
resources: [
path.resolve(__dirname, '../node_modules/bootstrap/scss/_functions.scss'),
path.resolve(__dirname, '../src/scss/config/**/*.scss'),
path.resolve(__dirname, '../node_modules/bootstrap/scss/mixins/**/*.scss'),
path.resolve(__dirname, '../src/scss/helpers/**/*.scss'),
]
}
}
]
},
]
},
plugins: [
// Makes some environment variables available to the JS code, for example:
// if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`.
new webpack.DefinePlugin(env.stringified),
],
node: {
console: false,
global: false,
process: false,
Buffer: false,
__filename: false,
__dirname: false,
setImmediate: false,
}
};
module.exports = merge(base, config);
Not 100% clear on what you're trying to do, but it sounds like you want to pass only some of the environment vars to the DefinePlugin, not all of them...? If that's the case you can just write a little fn to filter or whitelist the env vars and pass the result to DefinePlugin. If an env var isn't included in your DefinePlugin, webpack will be unaware of it at build time.
getClientEnvironment from env.js returned an object of key values which was then passed to DefinePlugin.
Although environment variables such as process.env.PORT weren't being passed to DefinePlugin they were being complied by Webpack as undefined.
This was due to getClientEnviroment returning the environment variables as an object of process.env
{ 'process.env':
{ NODE_ENV: '"production"',
PUBLIC_URL: '""',
REACT_APP_HOST: '"localhost"',
REACT_APP_PORT: '"8080"'
}
}
What I didn't realise was that in doing this, DefinePlugin overrides all process.env variables.
As per definePlugin's docs:
When defining values for process prefer 'process.env.NODE_ENV':
JSON.stringify('production') over process: { env: { NODE_ENV:
JSON.stringify('production') } }. Using the latter will overwrite the
process object which can break compatibility with some modules that
expect other values on the process object to be defined.

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.

Resources