I am writing mt first cli app with node and I am facing some issues using babel to transpile my code.
Basically, the app should start an express server which does ssr for react (similar to what next does).
Somewhere in the process I use jsx syntax to render react component, so I need to transpile my code with babel.
I am familiar on how to do this with babel cli or with webpack,
howevere, I`m still facing issues implementing it for cli app.
In my package.json file I have:
"bin": {
"ssr": "./cli/ssr.js"
},
and my ssr.js file:
#!/usr/bin/env node
const server = require('../server');
const routes = require('../../routes.js');
const createStore = require('redux').createStore;
const port = process.env.PORT || 3000;
const args = process.argv;
const defReducer = function(state={}, action){
return state;
}
const configureStore = createStore(defReducer);
const instance = server(routes, configureStore, {}, {});
instance.listen(port, ()=>{
console.log(`ssr server runs on localhost://${port}`);
});
and my server.js file is just a regular express server:
const express = require('express');
const cors = require('cors');
const renderer = require('./renderer');
module.exports = (Routes, createStore=()=>null, renderOpts={}, routerContext={})=>{
const app = express();
app.use(express.static('public'));
app.use(cors());
const port = process.env.PORT || 3000;
app.get('*.js', (req, res, next) => {
req.url = req.url + '.gz';
res.set('Content-Encoding', 'gzip');
next();
});
app.all('*', (req, res) => {
const store = createStore();
const promises = matchRoutes(Routes, req.path).map(( { route } ) => {
if (typeof route.path === 'undefined') { return null; }
let ctx = {store, module:route.module, req, res}
return route.loadData ? route.loadData(ctx) : null;
});
Promise.all(promises).then(() => {
const content = renderer(Routes, req, store, renderOpts, routerContext);
if (context.url) {
return res.redirect(301, context.url);
}
if (context.notFound) {
res.status(404);
}
res.send(content);
});
});
return app;
}
inside server.js file I call renderer which does:
const content = renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={routerContext} basename= {opts.baseName || ''}>
<div>{renderRoutes(Routes)}</div>
</StaticRouter>
</Provider>
);
and this is where I get my syntax errors...
I also tried to precompile my server.js file using webpack and babel
and than link the bin command to the bundle.js output but it didn`t work
I get this error popping on the screen:
What is the correct way of using babel with cli app?
I followed a few steps here which you can find by going here https://babeljs.io/setup and clicking "CLI". I was able to transpile your server.js JSX following those steps, plus a couple extra in a fresh new folder. In order to transpile your code, here's what I did:
Created a package.json by running (used all default values)
npm init
Created src\server.js file with your small <Provider> excerpt above
Ran the following commands to install babel and the react libraries:
npm install --save-dev #babel/core #babel/cli
npm install --save-dev #babel/preset-env
npm install --save-dev #babel/preset-react
Created a .babelrc file with this single line:
{ "presets": ["#babel/preset-env", "#babel/preset-react"] }
Created build script in package.json
scripts: { "build": "babel src -d lib" }
Ran the build:
npm run-script build
And it successfully ran and transpiled the js into a new file within the lib folder. Try it out in a brand new folder and let me know how goes works for you
Related
I have a basic MERN stack application. On the app when I do npm start, it runs fine and functions as expected. However, after doing a npm run build, and running serve -s build, the app fails with the following message -
TypeError: a.map is not a function
at He (Contacts.js:27:16)
at wl (react-dom.production.min.js:166:137)
at Pi (react-dom.production.min.js:215:270)
at wu (react-dom.production.min.js:291:202)
at ys (react-dom.production.min.js:279:389)
at vs (react-dom.production.min.js:279:320)
at ms (react-dom.production.min.js:279:180)
at as (react-dom.production.min.js:270:88)
at rs (react-dom.production.min.js:267:429)
at x (scheduler.production.min.js:13:203)
Here is my express main server.js file -
const express = require('express')
const cors = require('cors')
const app = express()
const connectDB = require('./config/db')
app.use(cors())
//Connect database
connectDB()
//Init Middleware
app.use(express.json({ extended: false }))
app.get('/', (req, res) =>
res.json({ msg: 'Welcome to the Digital RoloDex API'}))
//Define Routes
app.use('/api/users', require('./routes/users'))
app.use('/api/auth', require('./routes/auth'))
app.use('/api/contacts', require('./routes/contacts'))
const PORT = process.env.PORT || 4444
app.listen(PORT, () => console.log(`app running on ${PORT}`))
Here is the file where the error is coming from -
import React, { useEffect } from 'react'
import ContactItem from './ContactItem'
import Spinner from '../layout/Spinner'
import { getContacts, useContacts } from '../context/contact/ContactState'
const Contacts = () => {
const [ contactState, contactDispatch ] = useContacts()
const { contacts, filtered, loading } = contactState
useEffect(() => {
getContacts(contactDispatch)
}, [contactDispatch])
if (contacts === 0 && !loading) {
return <h4>Please add a contact</h4>
}
return (
<div>
{ contacts !== null && !loading ?
filtered.length !== 0 ?
filtered.map((contact) => (
<ContactItem key={contact._id} contact={contact} />
))
: contacts.map((contact) => (
<ContactItem key={contact._id} contact={contact} />
))
:
<Spinner />
}
</div>
)
}
export default Contacts
I found two stackoverflow questions with the same problem. I applied their tips, but it is not helping either. Any help would be greatly appreciated.
https://stackoverflow.com/questions/65069848/typeerror-a-map-is-not-a-function
https://stackoverflow.com/questions/64362803/map-is-not-a-function-when-getting-react-files-from-build-folder
I want to answer my own question just in case someone runs into the same error. Foremost, in a static build, any variables, components, functions, etc. are going to be renamed with abbreviated letters. Which is why it is stating "a".map is not a function.
In addition, my backend and client were in two separate folders. So my front end wasn't calling to my back end properly. I uninstalled cors since this would make my users' information vulnerable across the web. Instead, I put my backend folder and files in the root and kept the client in its own folder. From there, I cd. into my client folder and did an npm run build. The error went away and was able to call back to the express api.
I was trying to use gzip to reduce the size of the bundle of my current angular project. The bundle report claims that after using gzip it would reduce my product-build's size to about 30% or less. But I don't know how to make that work.
Currently, my project is built in Angular and is sent to a port through Node.js Express.js server. Guess we would need config both Angular and NodeJS to make it work?
What I have tried so far:
It looks like the gzip can be applied in Angular 5- until Google disabled the ng eject. Also, I found a lot of Nginx tutorials with gzip but none of Node.js by now. So guess I don't quite understand either of them.
I tried to add the following code in my node server, but it doesn't seem to affect the performance of the project with or without the compression.
const express = require('express');
const http = require('http');
const https = require('https');
const compression = require('compression');
const app = express();
const port = 4201;
app.use(compression());
app.use(express.static(__dirname + '/dist'));
Edit:
After testing with http://www.gidnetwork.com/tools/gzip-test.php, my site has been compressed into gzip. But the performance in Google Page Insight doesn't change. Perhaps only the index.html is compressed?
I use node version 16.13.2 and it has a built-in compression package.
So, all you need is to use the below server.ts file as your node server
import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '#nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '#angular/common';
import { existsSync } from 'fs';
const compression = require('compression');
// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
const server = express();
const distFolder = join(process.cwd(), 'dist/your-project-name/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
server.use(compression());
// Our Universal express-engine (found # https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
server.set('view engine', 'html');
server.set('views', distFolder);
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
// All regular routes use the Universal engine
server.get('*', (req, res) => {
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
});
return server;
}
function run(): void {
const port = process.env['PORT'] || 4000;
// Start up the Node server
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
run();
}
export * from './src/main.server';
Try zlib, nodejs has a stable support in core now.
An example (taken from the docs):
// server example
// Running a gzip operation on every request is quite expensive.
// It would be much more efficient to cache the compressed buffer.
var zlib = require('zlib');
var http = require('http');
var fs = require('fs');
http.createServer(function(request, response) {
var raw = fs.createReadStream('index.html');
var acceptEncoding = request.headers['accept-encoding'];
if (!acceptEncoding) {
acceptEncoding = '';
}
// Note: this is not a conformant accept-encoding parser.
// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
if (acceptEncoding.match(/\bdeflate\b/)) {
response.writeHead(200, { 'content-encoding': 'deflate' });
raw.pipe(zlib.createDeflate()).pipe(response);
} else if (acceptEncoding.match(/\bgzip\b/)) {
response.writeHead(200, { 'content-encoding': 'gzip' });
raw.pipe(zlib.createGzip()).pipe(response);
} else {
response.writeHead(200, {});
raw.pipe(response);
}
}).listen(1337);
for angular you have to install gzip-all
npm i gzip-all
and on your package.json file
package.json
"scripts": {
"build": "ng build --prod --no-delete-output-path --aot --base-href /app/ && gzip-all \Project file.....\" ",
},
I have been closely trying to follow this Vue SSR guide and in that example the author has placed his Express routes within a file called server.js. Simply for organisation purposes, I wish to keep my Express routes in a router.express.js file within my src/router folder instead of in the root file of server.js.
So this is a cutdown version of my router.express.js file:
const vueServerRenderer = require('vue-server-renderer');
const setupDevServer = require('../../build/setup-dev-server'); //webpack dev config
const express = require('express');
const app = express();
const router = express.Router();
const createRenderer = (serverBundle) =>
vueServerRenderer.createBundleRenderer(serverBundle, {
runInNewContext: false,
template: fs.readFileSync(path.resolve(__dirname, '../index.html'), 'utf-8')
});
let renderer;
if (process.env.NODE_ENV === 'development') {
setupDevServer(app, (serverBundle) => {
renderer = createRenderer(serverBundle);
});
} else {
renderer = createRenderer(require('../../dist/vue-ssr-server-bundle.json'));
}
router.get('/', async function (req, res) {
const context = {
url: req.params['0'] || '/'
};
let html;
try {
html = await renderer.renderToString(context);
} catch (error) {
if (error.code === 404) {
return res.status(404).send('404 | Page Not Found');
}
return res.status(500).send('500 | Internal Server Error');
}
res.end(html);
});
module.exports = router;
The problem is that I also need the vue-server-renderer code to be in the server.js file. I would then make the app require the router.express.js file so the Express routes work like this:
const vueServerRenderer = require('vue-server-renderer');
const setupDevServer = require('../../build/setup-dev-server'); //webpack dev config
const app = express();
const createRenderer = (serverBundle) =>
vueServerRenderer.createBundleRenderer(serverBundle, {
runInNewContext: false,
template: fs.readFileSync(path.resolve(__dirname, '../index.html'), 'utf-8')
});
let renderer;
if (process.env.NODE_ENV === 'development') {
setupDevServer(app, (serverBundle) => {
renderer = createRenderer(serverBundle);
});
} else {
renderer = createRenderer(require('../../dist/vue-ssr-server-bundle.json'));
}
app.use(require('./router/express.router.js'));
Whenever I do this, I get a Webpack error stating that
WebpackOptionsValidationError: Invalid configuration object. Webpack
has been initialised using a configuration object that does not match
the API schema.
- configuration.entry'app' should be a string.
If I remove the vue-server-renderer code from server.js then it runs fine. But the reason for having that code within server.js is so that the development environment works properly. Basically if the code is not in server.js then I cannot use hot-reloading or anything.
If I get rid of router.express.js and put all that code in server.js including the routes, then it all works perfectly including my development environment.
Why can I not (or rather how can I) keep my Express routes in a separate file and still have the vue-server-renderer stuff work?
Update: setup-dev-server.js file:
const setupDevServer = (app, onServerBundleReady) => {
const webpack = require('webpack');
const MFS = require('memory-fs');
const path = require('path');
const clientConfig = require('./webpack.client.config');
const serverConfig = require('./webpack.ssr.config');
// additional client entry for hot reload
clientConfig.entry.app = ['webpack-hot-middleware/client', clientConfig.entry.app];
const clientCompiler = webpack(clientConfig);
// setup dev middleware
app.use(require('webpack-dev-middleware')(clientCompiler, {
publicPath: clientConfig.output.publicPath,
serverSideRender: true,
logLevel: 'silent',
}));
// setup hot middleware
app.use(require('webpack-hot-middleware')(clientCompiler));
// watch src files and rebuild SSR bundle
global.console.log('Building SSR bundle...');
const serverCompiler = webpack(serverConfig);
const mfs = new MFS();
serverCompiler.outputFileSystem = mfs;
serverCompiler.watch({}, (error, stats) => {
if (error) throw error;
global.console.log(
`${stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false,
})}\n\n`
);
if (stats.hasErrors()) {
console.error(stats.compilation.errors);
throw new Error(stats.compilation.errors);
}
// read bundle generated by vue-ssr-webpack-plugin
const bundle = JSON.parse(
mfs.readFileSync(path.join(clientConfig.output.path, 'vue-ssr-server-bundle.json'), 'utf-8')
);
onServerBundleReady(bundle);
});
};
module.exports = setupDevServer;
1) Inside of your 'router.express.js' file write: module.exports = router; at the bottom of the file.
2) Inside of your 'server.js' file write: const router = require('./router/express.router.js');
at the top of the file.
3) And now where you used to have app.use(require('./router/express.router.js'));
replace that with app.use(router);
4) At the top of 'server.js' write const vueSsrBundle = require('../../dist/vue-ssr-server-bundle.json')
5) Lastly replace renderer = createRenderer(require('../../dist/vue-ssr-server-bundle.json')
With renderer = createRenderer(vueSsrBundle)
EDIT the issue you are facing is on this line And probably related to the file 'app.js' or 'client-entry-js'
I am running an NestJS Angular Universal app on my local server using Angular 9 and Ivy. I can get everything to work when I run:
npm run serve:ssr
However, nothing loads unless I type the route manually. I would think it would automatically load "index.html" without having to type it in.
localhost:8080 ----- nothing
localhost:8080/index.html ---- works
Is there a way to modify the code to do a rewrite for the root path? I would think this would not be necessary:
main.ts
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api');
await app.listen(process.env.PORT || 8080);
}
// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = (mainModule && mainModule.filename) || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
bootstrap().catch(err => console.error(err));
}
Or fix the problem at hand...
After days of working on this, I figured out I had a loop by including a component that was including another component which had a loop in the ts file. In my case I was subscribing to something I shouldn't have. I thought this was an IVY / NestJS compatibility problem, but turns out it was my code.
I'm developing a NextJS application and have been using npm run dev during the development process. Now I'm trying to do a production build as described on the GitHub page.
My app blows up in production mode; it seems the cookie-parser node middleware is not installed in the production build? Here is how I set it up:
server.js
const express = require('express');
const next = require('next');
const cookieParser = require('cookie-parser'); // require cookie-parser
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare()
.then(() => {
const server = express();
server.use(cookieParser()); // use cookieParser
server.get('*', (req, res) => {
return handle(req, res);
});
server.listen(3000, (err) => {
if (err) throw err;
console.log('> Ready on http://localhost:3000');
});
})
.catch((ex) => {
console.error(ex.stack);
process.exit(1);
})
Later in the code I access the node req object. In development mode req.cookies is present as I would expect. In production mode it's absent.
It looks like there is no server.js file in the production build directory. What's more, grepping for cookie-parser and cookieParser in said production build directory yields empty results.
Any idea what's going on and how to get cookie-parser working in a production NextJS build?
Found the answer on the same GitHub page.
When using a custom server with a server file, for example called
server.js, make sure you update the scripts key in package.json to:
{
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
}
}
One problem with the production build down!