I'm encountering this error on npm run start:
TypeError: Cannot read property 'pathname' of undefined
Which is getting thrown by:
ReactDOMServer.renderToString(element)
Edit: Got some better logging and found that createResolver(fetcher) is returning an object with:
lastQueries: [ undefined, undefined ],
/src/server.js
import path from 'path';
import express from 'express';
import bodyParser from 'body-parser';
import httpProxy from 'http-proxy';
import { getFarceResult } from 'found/lib/server';
import ReactDOMServer from 'react-dom/server';
import serialize from 'serialize-javascript';
import { ServerFetcher } from './fetcher';
import { createResolver, historyMiddlewares, render, routeConfig } from './router';
const {PRIVATE_IP, API_IP, PORT, API_PORT} = process.env;
const publicPath = path.join(__dirname, '/..', 'public');
export default parameters => {
const app = express();
const proxy = httpProxy.createProxyServer({ ignorePath: true });
const proxyOptions = {
target: `http://${API_IP}:${API_PORT}/graphql-api`,
ignorePath: true,
};
function getFromProxy (req, res) {
req.removeAllListeners('data');
req.removeAllListeners('end');
process.nextTick(_ => {
if (req.body) {
req.emit('data', JSON.stringify(req.body));
}
req.emit('end');
});
proxy.web(req, res, proxyOptions);
}
app.use(express.static(publicPath));
app.use(bodyParser.json({ limit: '1mb' }));
app.use('/graphql-api', getFromProxy);
app.use(async (req, res) => {
const fetcher = new ServerFetcher(`http://${PRIVATE_IP}:${PORT}/graphql-api`);
const { redirect, status, element } = await getFarceResult({
url: req.url,
historyMiddlewares,
routeConfig,
resolver: createResolver(fetcher),
render,
});
if (redirect) {
res.redirect(302, redirect.url);
return;
}
res.status(status).send(`
<!DOCTYPE html>
<html lang="en">
...
<body>
<div id="root">${ReactDOMServer.renderToString(element)}</div>
<script>
window.__RELAY_PAYLOADS__ = ${serialize(fetcher, { isJSON: true })};
</script>
<script src="/bundle.js"></script>
</body>
</html>
`);
});
app.listen(PORT, PRIVATE_IP, err => {
if (err) {
console.log(`[Error]: ${err}`);
}
console.info(`[express server]: listening on ${PRIVATE_IP}:${PORT}`);
});
};
Other files:
/package.json
"scripts": {
"schema": "gulp load-schema",
"relay": "relay-compiler --src ./src --schema ./data/schema.graphql",
"start": "npm-run-all schema relay prepare-server-build start-development-workflow",
"start-development-workflow": "npm-run-all --parallel development-webpack-build-for-client development-webpack-build-for-server development-start-server",
"prepare-server-build": "universal-webpack --settings ./webpack.isomorphic.settings.json prepare",
"development-webpack-build-for-client": "webpack-dev-server --hot --inline --config \"./webpack.isomorphic.client.babel.js\" --port 8080 --colors",
"development-webpack-build-for-server": "webpack --watch --config \"./webpack.isomorphic.server.babel.js\" --colors",
"development-start-server": "nodemon \"./start-server.babel\" --watch \"./build/dist/server\"",
...
},
/start-server.babel
require('babel-register')({ ignore: /\/(build|node_modules)\// });
require('babel-polyfill');
require('./src/start-server.js');
/src/start-server
import 'source-map-support/register';
import startServer from 'universal-webpack/server';
import settings from '../webpack.isomorphic.settings.json';
import configuration from '../webpack.config';
startServer(configuration, settings);
/webpack.config
import webpack from 'webpack';
import path from 'path';
import env from 'gulp-env';
import CopyWebpackPlugin from 'copy-webpack-plugin';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
// process.traceDeprecation = true;
if (!process.env.NODE_ENV) {
env({file: './.env', type: 'ini'});
}
const {
NODE_ENV,
PRIVATE_IP,
API_IP,
PORT,
API_PORT,
GOOGLE_ANALYTICS_KEY,
} = process.env;
const PATHS = {
root: path.join(__dirname),
src: path.join(__dirname, 'src'),
public: path.join(__dirname, 'build', 'public'),
shared: path.join(__dirname, 'src', 'shared'),
fonts: path.join(__dirname, 'src', 'shared', 'fonts'),
robots: path.join(__dirname, 'src', 'robots.txt'),
};
let devtool;
const plugins = [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(NODE_ENV),
PRIVATE_IP: JSON.stringify(PRIVATE_IP),
API_IP: JSON.stringify(API_IP),
PORT: JSON.stringify(PORT),
API_PORT: JSON.stringify(API_PORT),
GOOGLE_ANALYTICS_KEY: JSON.stringify(GOOGLE_ANALYTICS_KEY),
},
}),
new ExtractTextPlugin('styles.css'),
];
if (NODE_ENV === 'production') {
devtool = 'source-map';
plugins.push(
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
screw_ie8: true,
},
}),
new webpack.NoErrorsPlugin(),
new CopyWebpackPlugin([
{ from: PATHS.robots, to: PATHS.public },
]),
);
} else {
devtool = 'eval-source-map';
plugins.push(
new webpack.NamedModulesPlugin(),
);
}
const config = {
devtool,
context: PATHS.root,
entry: [
PATHS.src,
],
output: {
path: PATHS.public,
filename: 'bundle.js',
publicPath: '/',
},
plugins,
module: {
rules: [
{
test: /\.js$/,
include: PATHS.src,
loader: 'babel-loader',
},
{
test: /\.css$/,
include: PATHS.src,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: {
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[name]_[local]__[hash:base64:5]',
},
},
}),
},
{
test: /\.svg$/,
include: PATHS.src,
use: [
{ loader: 'url-loader', options: { limit: 10000 } },
],
},
{
test: /\.png$/,
include: PATHS.src,
use: [
{ loader: 'url-loader', options: { limit: 65000 } },
],
},
{
test: /\.(woff|woff2)$/,
include: PATHS.fonts,
loader: 'url-loader',
options: {
name: 'font/[hash].[ext]',
limit: 50000,
mimetype: 'application/font-woff',
},
},
],
},
resolve: {
modules: [
PATHS.src,
'node_modules',
],
alias: {
root: PATHS.root,
},
},
};
export default config;
/webpack.isomorphic.settings.json
{
"server": {
"input": "./src/server.js",
"output": "./build/dist/server.js"
}
}
/webpack.isomorphic.client.babel.js
import { client } from 'universal-webpack/config';
import settings from './webpack.isomorphic.settings.json';
import configuration from './webpack.config';
export default client(configuration, settings);
/webpack.isomorphic.server.babel.js
import { server } from 'universal-webpack/config';
import settings from './webpack.isomorphic.settings.json';
import configuration from './webpack.config';
export default server(configuration, settings);
/src/fetcher.js
import 'isomorphic-fetch';
// TODO: Update this when someone releases a real, production-quality solution
// for handling universal rendering with Relay Modern. For now, this is just
// enough to get things working.
class FetcherBase {
constructor (url) {
this.url = url;
}
async fetch (operation, variables) {
const response = await fetch(this.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ query: operation.text, variables }),
});
return response.json();
}
}
export class ServerFetcher extends FetcherBase {
constructor (url) {
super(url);
this.payloads = [];
}
async fetch (...args) {
const i = this.payloads.length;
this.payloads.push(null);
const payload = await super.fetch(...args);
this.payloads[i] = payload;
return payload;
}
toJSON () {
return this.payloads;
}
}
export class ClientFetcher extends FetcherBase {
constructor (url, payloads) {
super(url);
this.payloads = payloads;
}
async fetch (...args) {
if (this.payloads.length) {
return this.payloads.shift();
}
return super.fetch(...args);
}
}
/src/router.js
import queryMiddleware from 'farce/lib/queryMiddleware';
import createRender from 'found/lib/createRender';
import makeRouteConfig from 'found/lib/makeRouteConfig';
import Route from 'found/lib/Route';
import { Resolver } from 'found-relay';
import React from 'react';
import { Environment, Network, RecordSource, Store } from 'relay-runtime';
// static
import CorePage from 'core/components/CorePage';
import LoadingComponent from 'core/components/LoadingComponent';
import ErrorComponent from 'core/components/ErrorComponent';
import HomePage from 'home/components/HomePage';
import NotFound from 'not-found/components/NotFoundPage';
// user
import UserContainer from 'user/containers/UserContainer';
import UserContainerQuery from 'user/queries/UserContainerQuery';
export const historyMiddlewares = [queryMiddleware];
export function createResolver (fetcher) {
const environment = new Environment({
network: Network.create((...args) => fetcher.fetch(...args)),
store: new Store(new RecordSource()),
});
return new Resolver(environment);
}
export const routeConfig = makeRouteConfig(
<Route path={'/'} Component={CorePage}>
<Route Component={HomePage} />
<Route
path={'user/:userId'}
Component={UserContainer}
query={UserContainerQuery}
/>
<Route path={'*'} component={NotFound} />
</Route>,
);
export const render = createRender({
renderPending: _ => <LoadingComponent />,
renderError: error => {
console.error(`Relay renderer ${error}`);
return <ErrorComponent />; // renderArgs.retry?
},
});
/src/index.js
import BrowserProtocol from 'farce/lib/BrowserProtocol';
import createInitialFarceRouter from 'found/lib/createInitialFarceRouter';
import React from 'react';
import ReactDOM from 'react-dom';
import injectTapEventPlugin from 'react-tap-event-plugin';
import { AppContainer } from 'react-hot-loader';
import { ClientFetcher } from './fetcher';
import { createResolver, historyMiddlewares, render, routeConfig } from './router';
injectTapEventPlugin();
(async () => {
// eslint-disable-next-line no-underscore-dangle
const fetcher = new ClientFetcher('/graphql-api', window.__RELAY_PAYLOADS__);
const resolver = createResolver(fetcher);
const Router = await createInitialFarceRouter({
historyProtocol: new BrowserProtocol(),
historyMiddlewares,
routeConfig,
resolver,
render,
});
const rootRender = Component => {
ReactDOM.render(
<AppContainer>
<Component resolver={resolver} />
</AppContainer>,
document.getElementById('root'),
);
};
rootRender(Router);
})();
I had some legacy code for a component that was referencing context.router.isActive from when I was using react-router-relay. Getting rid of that cleared up the problem
Related
I'm posting this as a self-answered question because I didn't find anything that helped on the web when I was debugging this issue.
For some reason, the SSR version of my Angular 7 application consistently has a TTFB of around 5 seconds. I'm using the standard setup for Angular Universal using Express with a few modifications like so:
// These are important and needed before anything else
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { renderModuleFactory } from '#angular/platform-server';
import { enableProdMode } from '#angular/core';
import * as express from 'express';
import { join } from 'path';
import { readFileSync } from 'fs';
import { provideModuleMap } from '#nguniversal/module-map-ngfactory-loader';
// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();
// Express server
const compression = require('compression');
const app = express();
app.use(compression());
const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist');
const domino = require('domino');
const template = readFileSync(join(DIST_FOLDER , 'browser', 'index.html')).toString();
const win = domino.createWindow(template);
global['window'] = win;
global['Node'] = win.Node;
global['navigator'] = win.navigator;
global['Event'] = win.Event;
global['KeyboardEvent'] = win.Event;
global['MouseEvent'] = win.Event;
global['Event']['prototype'] = win.Event.prototype;
global['document'] = win.document;
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main');
app.engine('html', (_, options, callback) => {
renderModuleFactory(AppServerModuleNgFactory, {
// Our index.html
document: template,
url: options.req.url,
// DI so that we can get lazy-loading to work differently (since we need it to just instantly render it)
extraProviders: [
provideModuleMap(LAZY_MODULE_MAP)
]
}).then(html => {
callback(null, html);
});
});
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));
// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));
// All regular routes use the Universal engine
app.get('*', (req, res) => {
res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req });
});
// Start up the Node server
app.listen(PORT, () => {
console.log(`Node server listening on http://localhost:${PORT}`);
});
I've tried caching the pages on the server and removing the non-standard Domino stuff to make it just like the tutorial, but nothing changes the TTFB. That leads me to believe there is something in the application code that could be slowing it down. Any ideas?
When thinking about it more, I remembered that I had added a 5 second delay on pre-loading modules so that users who bounce from the application don't end up having to download all of the lazy loaded bundles. The code was like so:
import { PreloadingStrategy } from "#angular/router";
import { Route } from "#angular/router";
import { RouterModule } from "#angular/router";
import { Routes } from "#angular/router";
import { NgModule } from "#angular/core";
import { Observable, timer } from "rxjs";
import { of } from 'rxjs/internal/observable/of';
import { flatMap } from "rxjs/internal/operators";
export class PreloadPublicModules implements PreloadingStrategy {
preload(route : Route, load : Function) : Observable<any> {
const loadRoute = (delay) => delay ? timer(5000).pipe(flatMap(_ => load())) : load();
return route.data && route.data.preload ? loadRoute(5000) : of(null);
}
}
const routes : Routes = [
{ path: '', loadChildren: "app/public/home/home.module#HomeModule", data: { preload: true } },
{ path: 'about', loadChildren: "app/public/about/about.module#AboutModule", data: { preload: true } },
{ path: 'contact', loadChildren: "app/public/contact/contact.module#ContactModule", data: { preload: true } },
{ path: 'dashboard', loadChildren: "app/internal/dashboard/dashboard.module#DashboardModule", data: { preload: false } },
];
#NgModule({
imports : [ RouterModule.forRoot(routes, { preloadingStrategy: PreloadPublicModules }) ],
exports : [ RouterModule ],
providers : [ PreloadPublicModules ]
})
export class AppRoutingModule { }
Removing that delay fixed the issue for the SSR portion; it no longer has the 5s TTFB. The PreloadPublicModules function is now:
export class PreloadPublicModules implements PreloadingStrategy {
preload(route : Route, load : Function) : Observable<any> {
return route.data && route.data.preload ? load() : of(null);
}
}
I have tweo problems.
First, I can't pass a queryParam from server.ts to AppCompoment.ts.
Second, the service translateService throws ERROR [Error] without message when is executed on the server, but works fine when is executed on the browser.
Here the code parts of the projec:
server.ts
// These are important and needed before anything else
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { renderModuleFactory } from '#angular/platform-server';
import { enableProdMode } from '#angular/core';
import * as express from 'express';
import { join } from 'path';
import { readFileSync } from 'fs';
(global as any).WebSocket = require('ws');
(global as any).XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();
// Express server
const app = express();
//app.urlencoded({extended: false});
const PORT = process.env.PORT || 4000;
const HTTPS_PORT = process.env.HTTPS_PORT || 4443;
const KEY_CERTIFICATE = process.env.KEY_CERTIFICATE;
const CRT_CERTIFICATE = process.env.CRT_CERTIFICATE;
const PASSWORD_CERTIFICATE = process.env.PASSWORD_CERTIFICATE;
const DIST_FOLDER = join(process.cwd(), 'dist');
// Our index.html we'll use as our template
const template = readFileSync(join(DIST_FOLDER, 'browser', 'index.html')).toString();
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main.bundle');
// Express Engine
import { ngExpressEngine } from '#nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '#nguniversal/module-map-ngfactory-loader';
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));
// Our page routes
export const routes: string[] = [
'main',
'dashboard',
'dashboard/contact',
'dashboard/blog'
];
// All regular routes use the Universal engine
app.get('/', (req, res) => {
console.time(`GET: ${req.originalUrl}`);
res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req, res } );
console.timeEnd(`GET: ${req.originalUrl}`);
});
routes.forEach(route => {
app.get(`/${route}`, (req, res) => {
//res.json({'lang': req.query.lang});
console.log(req.query.lang);
console.time(`GET: ${req.originalUrl}`);
res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req, res } );
console.timeEnd(`GET: ${req.originalUrl}`);
});
app.get(`/${route}/*`, (req, res) => {
//res.json({'lang': req.query.lang});
console.log(req.query.lang);
console.time(`GET: ${req.originalUrl}`);
res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req, res } );
console.timeEnd(`GET: ${req.originalUrl}`);
});
});
// Server static files from /browser
app.get('/web', express.static(join(DIST_FOLDER, 'browser'), { 'index': false }));
app.get('/**', express.static(join(DIST_FOLDER, 'browser')));
// All other routes must be resolved if exist
/*
app.get('*', function(req, res) {
res.render(join(req.url), { req });
});
*/
var http = require('http');
var httpServer = http.createServer(app);
// Start up the Node server at PORT
httpServer.listen(PORT, () => {
console.log(`Node server listening on http://localhost:${PORT}`);
});
if(KEY_CERTIFICATE && CRT_CERTIFICATE && PASSWORD_CERTIFICATE) {
var fs = require('fs');
var https = require('https');
var privateKey = fs.readFileSync(KEY_CERTIFICATE, 'utf8');
var certificate = fs.readFileSync(CRT_CERTIFICATE, 'utf8');
var credentials = {
key: privateKey,
cert: certificate,
passphrase: PASSWORD_CERTIFICATE
};
var httpsServer = https.createServer(credentials, app);
// Start up the Node server at HTTP_PORT
httpsServer.listen(HTTPS_PORT, () => {
console.log(`Node server listening on http://localhost:${HTTPS_PORT}`);
});
}
AppServerModule.ts
import { NgModule } from '#angular/core';
import { ServerModule, ServerTransferStateModule } from '#angular/platform-server';
import { ModuleMapLoaderModule } from '#nguniversal/module-map-ngfactory-loader';
import { AppModule } from './app.module';
import { TemplateComponent } from '../template/template.component';
#NgModule({
imports: [
// The AppServerModule should import your AppModule followed
// by the ServerModule from #angular/platform-server.
AppModule,
ServerModule,
ModuleMapLoaderModule, // <-- *Important* to have lazy-loaded routes work
ServerTransferStateModule,
],
// Since the bootstrapped component is not inherited from your
// imported AppModule, it needs to be repeated here.
bootstrap: [TemplateComponent],
})
export class AppServerModule {
constructor() {
console.log('AppServerModule');
}
}
AppModule:
import { NgModule } from '#angular/core';
import { BrowserModule, BrowserTransferStateModule } from '#angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { HttpModule } from '#angular/http';
import { enableProdMode } from '#angular/core';
import { HttpClientModule, HttpClient } from '#angular/common/http';
import { TranslateHttpLoader } from '#ngx-translate/http-loader';
import { TranslateModule, TranslateLoader } from '#ngx-translate/core';
// Routing
import { Router } from '#angular/router';
import { RouterModule, Routes } from '#angular/router';
// Firebase
import { AngularFireModule } from 'angularfire2';
import { environment } from '../../environments/environment';
import { AngularFireDatabase } from 'angularfire2/database';
import { AngularFireAuthModule } from 'angularfire2/auth';
import { AuthService } from '../firebase-auth/auth.service';
import { FirebaseAuthComponent } from '../firebase-auth/firebase-auth.component';
// My Components
import { TemplateComponent } from '../template/template.component';
import { MainComponent } from '../main/main.component';
import { BlogComponent } from '../blog/blog.component';
import { ProjectsComponent } from '../projects/projects.component';
import { ProjectComponent } from '../project/project.component';
import { FormComponent } from '../form/form.component';
import { DashboardComponent } from '../dashboard/dashboard.component';
import { DashboardBlogComponent } from '../dashboard/dashboard-blog.component';
import { DashboardContactComponent } from '../dashboard/dashboard-contact.component';
import { ExperienceComponent } from '../experience/experience.component';
import { ProjectService } from '../projects/project.service';
import { ExperienceService } from '../experience/experience.service';
import { PipesModule } from '../pipes/pipes.module';
import { LanguageService } from '../template/language.service';
const appRoutes: Routes = [
{
path: 'main',
component: MainComponent
},
{
path: 'blog',
component: BlogComponent
},
{
path: 'dashboard',
component: DashboardComponent,
children: [
{
path: '',
redirectTo: 'blog',
pathMatch: 'full'
},
{
path: 'blog',
component: DashboardBlogComponent
},
{
path: 'contact',
component: DashboardContactComponent
}
]
},
{
path: '',
redirectTo: '/main',
pathMatch: 'prefix'
},
{
path: '**',
redirectTo: '/main',
pathMatch: 'prefix'
}
];
#NgModule({
declarations: [
FirebaseAuthComponent,
MainComponent,
BlogComponent,
TemplateComponent,
ProjectsComponent,
ProjectComponent,
FormComponent,
DashboardComponent,
DashboardBlogComponent,
DashboardContactComponent,
ExperienceComponent,
],
imports: [
BrowserModule.withServerTransition({appId: 'davidmartinezros.com'}),
BrowserTransferStateModule,
FormsModule,
ReactiveFormsModule,
HttpModule,
HttpClientModule,
RouterModule.forRoot(appRoutes),
AngularFireModule.initializeApp(environment.firebase),
AngularFireAuthModule,
PipesModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: (createTranslateLoader),
deps: [HttpClient]
}
})
],
providers: [AuthService, AngularFireDatabase, BlogComponent, DashboardContactComponent, ProjectService, ExperienceService, LanguageService],
bootstrap: [TemplateComponent] // main (first) component
})
export class AppModule {
constructor() {
console.log('AppModule');
}
}
/*
if (!/localhost/.test(document.location.host)) {
enableProdMode();
}
*/
export function createTranslateLoader(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
TemplateComponent.ts:
import { Component, ViewChild, ElementRef, Inject } from '#angular/core';
import { ActivatedRoute, RouterModule } from '#angular/router';
import { AuthService } from '../firebase-auth/auth.service';
import { TranslateService } from '#ngx-translate/core';
import { ProjectsComponent } from '../projects/projects.component';
import { ExperienceComponent } from '../experience/experience.component';
import { LanguageService } from './language.service';
import { Pipe } from '#angular/core';
import { Language } from './language';
import { Title, Meta } from '#angular/platform-browser';
import { PLATFORM_ID } from '#angular/core';
import { isPlatformBrowser, isPlatformServer } from '#angular/common';
#Component({
selector: 'app-template',
templateUrl: '../template/template.component.html'
})
export class TemplateComponent {
objectKeys = Object.keys;
languages: Language[];
language;
loaded: boolean = false;
playing: boolean = false;
sound: any;
constructor(#Inject(PLATFORM_ID) private platformId: Object,
public authService: AuthService,
private translate: TranslateService,
private route: ActivatedRoute,
private languageService: LanguageService,
private titleService: Title,
private metaService: Meta) {
this.getLanguanges();
}
ngOnInit() {
if (isPlatformBrowser(this.platformId)) {
// Client only code.
this.loadLanguage();
this.loadMusic();
}
if (isPlatformServer(this.platformId)) {
// Server only code.
this.loadServerLanguage();
}
}
loadMusic() {
this.sound = new Audio();
this.sound.autoplay = false;
this.sound.preload = 'auto';
this.sound.autobuffer = true;
let parent = this;
this.sound.addEventListener('loadeddata', function() {
parent.loaded = true;
}, false);
this.sound.addEventListener('play', function() {
parent.playing = true;
}, false);
this.sound.addEventListener('pause', function() {
parent.playing = false;
}, false);
this.sound.src = './assets/audio/Rhodesia_MkII.mp3';
this.sound.load();
}
loadLanguage() {
var userLang = "";
//console.log(this.route);
console.log(this.route.queryParams);
this.route.queryParams.subscribe(params => {
if(!params['lang'] || params['lang'] == "") {
userLang = this.language;
} else {
userLang = params['lang'];
}
console.log("queryParams:" + userLang);
if(!userLang || userLang == "") {
userLang = navigator.language;
if(userLang.startsWith("zh")) {
userLang = "zh";
}
}
if(userLang) {
userLang = userLang.toLowerCase();
}
if(userLang && userLang.length > 2) {
userLang = userLang.substring(0,2);
}
if(userLang == "es" || userLang == "en" || userLang == "zh") {
this.changeLanguage(userLang);
} else {
this.changeLanguage("en");
}
console.log('complete loadLanguage');
});
}
loadServerLanguage() {
var userLang = "";
//console.log(this.route);
console.log(this.route.queryParams);
this.route.queryParams.subscribe(params => {
if(!params['lang'] || params['lang'] == "") {
userLang = this.language;
} else {
userLang = params['lang'];
}
console.log("queryParams:" + userLang);
if(userLang) {
userLang = userLang.toLowerCase();
}
if(userLang && userLang.length > 2) {
userLang = userLang.substring(0,2);
}
if(userLang == "es" || userLang == "en" || userLang == "zh") {
this.changeLanguage(userLang);
} else {
this.changeLanguage("en");
}
console.log('complete loadLanguage');
});
}
isLoadedTrack() {
return this.loaded;
}
isPlayingTrack() {
return this.playing;
}
playTrack() {
if(this.sound) {
this.sound.play();
}
}
pauseTrack() {
if(this.sound) {
this.sound.pause();
}
}
public changeServerLanguage(language) {
console.log(language);
console.log("Ara anem a cridar a this.translate.setDefaultLang(language); que al servidor dona error.");
// this language will be used as a fallback when a translation isn't found in the current language
this.translate.setDefaultLang(language);
// the lang to use, if the lang isn't available, it will use the current loader to get them
this.translate.use(language);
this.language = language;
// Sets the <title></title>
this.translate.get("TitleIndex")
.toPromise()
.then(title => this.titleService.setTitle(title))
.catch(this.handleError);
// Sets the <meta> tag author
this.translate.get("TagAuthorIndex")
.toPromise()
.then(author => this.metaService.updateTag({ name: 'author', content: author }))
.catch(this.handleError);
// Sets the <meta> tag keywords
this.translate.get("TagKeywordsIndex")
.toPromise()
.then(keywords => this.metaService.updateTag({ name: 'keywords', content: keywords }))
.catch(this.handleError);
// Sets the <meta> tag description
this.translate.get("TagDescriptionIndex")
.toPromise()
.then(description => this.metaService.updateTag({ name: 'description', content: description }))
.catch(this.handleError);
console.log('changeServerLanguage');
}
public changeLanguage(language) {
console.log(language);
// this language will be used as a fallback when a translation isn't found in the current language
this.translate.setDefaultLang(language);
// the lang to use, if the lang isn't available, it will use the current loader to get them
this.translate.use(language);
this.language = language;
ProjectsComponent.updateStuff.next(false);
ExperienceComponent.updateStuff.next(false);
this.getLanguanges();
// Sets the <title></title>
this.translate.get("TitleIndex")
.toPromise()
.then(title => this.titleService.setTitle(title))
.catch(this.handleError);
// Sets the <meta> tag author
this.translate.get("TagAuthorIndex")
.toPromise()
.then(author => this.metaService.updateTag({ name: 'author', content: author }))
.catch(this.handleError);
// Sets the <meta> tag keywords
this.translate.get("TagKeywordsIndex")
.toPromise()
.then(keywords => this.metaService.updateTag({ name: 'keywords', content: keywords }))
.catch(this.handleError);
// Sets the <meta> tag description
this.translate.get("TagDescriptionIndex")
.toPromise()
.then(description => this.metaService.updateTag({ name: 'description', content: description }))
.catch(this.handleError);
}
getLanguanges(): void {
this.languageService.getLanguages()
.then(languages =>
{ this.languages = languages }
);
}
private handleError(error: any): Promise<any> {
console.error('An error occurred', error); // for demo purposes only
return Promise.reject(error.message || error);
}
}
I'm trying to setup an own express server with webpack to use hot module replacement & server side rendering. Everything seems to work except the integration of webpack-hot-server-middleware which needs an express middleware function with (res, req, next) params - but I can't get my head around about how to implement it correctly.
This is my configuration so far:
webpack.config.js
const webpack = require('webpack');
const path = require('path');
const config = [{
name: 'client',
entry: [
'webpack-hot-middleware/client',
'./src/js/entry_client.js'
],
output: {
path: path.resolve(__dirname, 'dist/js/'),
filename: 'app.js'
},
module: {
rules: [{
test: /\.vue$/,
loader: 'vue-loader'
}, {
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}]
},
resolve: {
alias: {
vue$: 'vue/dist/vue.runtime.js'
}
}
}, {
name: 'server',
target: 'node',
entry: './src/js/entry_server.js',
output: {
path: path.resolve(__dirname, 'dist/js/'),
filename: 'app.server.js',
libraryTarget: 'commonjs2'
},
externals: require('webpack-node-externals')(),
module: {
rules: [{
test: /\.vue$/,
loader: 'vue-loader'
}]
},
resolve: {
alias: {
vue$: 'vue/dist/vue.runtime.js',
}
}
}];
if (process.env.NODE_ENV !== 'production') {
config[0].plugins = [new webpack.HotModuleReplacementPlugin()];
}
module.exports = config;
entry_client.js
import {createApp} from './app';
const {app, router} = createApp();
router.onReady(() => {
app.$mount('#app');
});
entry_server.js
import {createApp} from './app';
export default context => {
return new Promise((resolve, reject) => {
const {app, router} = createApp();
router.push(context.url);
router.onReady(() => {
if (!router.getMatchedComponents().length) return reject({code: 404});
resolve(app);
}, reject);
});
};
app.js
import Vue from 'vue';
import App from '../vue/app.vue';
import {createRouter} from './router';
export function createApp() {
const router = createRouter();
const app = new Vue({
router,
render: h => h(App)
});
return {app, router};
}
router.js
import Vue from 'vue';
import Router from 'vue-router';
import Home from '../vue/home.vue';
import About from '../vue/about.vue';
Vue.use(Router);
export function createRouter() {
return new Router({
mode: 'history',
routes: [{
path: '/',
component: Home
}, {
path: '/about',
component: About
}]
});
}
server.js
const express = require('express');
const fs = require('fs');
const path = require('path');
const bundle = require('./dist/js/app.server');
const renderer = require('vue-server-renderer').createRenderer({
template: fs.readFileSync('./src/html/index.html', 'utf-8')
});
const server = express();
// add vue HMR middleware
if (process.env.NODE_ENV !== 'production') {
const webpack = require('webpack');
const compiler = webpack(require('./webpack.config'));
server.use(require('webpack-dev-middleware')(compiler, {
noInfo: true,
serverSideRender: true
}));
server.use(require('webpack-hot-middleware')(compiler.compilers.find(compiler => compiler.name === 'client')));
// server.use(require('webpack-hot-server-middleware')(compiler));
} else {
// static file serving
server.use(require('serve-favicon')(path.join(__dirname, 'src/png/favicon-32x32.png')));
server.use(express.static(path.join(__dirname, 'dist/'), {index: false}));
}
server.get('*', (req, res) => {
bundle.default({url: req.url}).then(app => {
renderer.renderToString(app, {
title: 'Some title',
description: 'Some description'
}, (err, html) => {
if (err) {
console.error('Error in renderToString:', err);
return res.status(500).send('Internal Server Error');
}
res.send(html);
});
}, err => {
if (err.code === 404) return res.status(404).send('Page not found');
console.error(err);
return res.status(500).send('Internal Server Error');
});
});
server.listen(4040);
I want to proxy all /bundle urls to webpack dev server.
I have followed the advice from this question which does the same thing.
However, I still can't see my publicFiles served from localhost:8081/bundle? The silly thing is I have another project not based on react which works just fine, but this just says:
Cannot GET /bundle/
when accessing localhost:8081/bundle .
If you can point out what I'm missing I would really appreciate it. It must be something silly since I know this has to work.
Working in development mode
Here is my webpack config
'use strict';
var webpack = require('webpack');
const autoprefixer = require('autoprefixer');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var CompressionPlugin = require('compression-webpack-plugin');
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
let getPlugins;
var isProd = (process.env.NODE_ENV === 'production');
console.log("Production: " + isProd);
let entryFill = null;
let publicPathFill = null;
if (isProd) {
entryFill = {
bootstrap: ['bootstrap/dist/css/bootstrap.css', 'bootstrap/dist/js/bootstrap.js'],
index: ['./src/bundle/index.tsx'],
vendor: ['react', 'react-dom', 'jquery', 'jquery-ui-bundle', "redux-thunk", 'redux', 'react-redux']
}
publicPathFill = "./dist/assets/bundle";
getPlugins = function () {
return [
new SWPrecacheWebpackPlugin(
{
cacheId: 'cleaning-website',
filename: 'service-worker.js',
maximumFileSizeToCacheInBytes: 4194304,
runtimeCaching: [{
handler: 'cacheFirst',
urlPattern: /[.]js$/
}],
}
),
new ExtractTextPlugin("site.css"),
new webpack.optimize.UglifyJsPlugin(),
new webpack.optimize.OccurrenceOrderPlugin(),
//new webpack.optimize.DedupePlugin(),
//new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.bundle.js'),
//new webpack.optimize.AggressiveMergingPlugin(),
new webpack.ProvidePlugin({
jQuery: 'jquery',
$: 'jquery',
jquery: 'jquery'
})
//new CompressionPlugin({
// asset: "[path].gz[query]",
// algorithm: "gzip",
// test: /\.js$|\.css$|\.tsx$/,
// threshold: 10240,
// minRatio: 0.8
//})
]
}
} else {
entryFill = {
bootstrap: ['bootstrap/dist/css/bootstrap.css', 'bootstrap/dist/js/bootstrap.js', 'webpack/hot/dev-server', 'webpack-dev-server/client?http://localhost:8081'],
index: ['./src/bundle/index.tsx', 'webpack/hot/dev-server', 'webpack-dev-server/client?http://localhost:8081'],
vendor: ['react', 'react-dom', 'jquery', 'jquery-ui-bundle', "redux-thunk", 'redux', 'react-redux', 'webpack/hot/dev-server', 'webpack-dev-server/client?http://localhost:8081']
}
publicPathFill = "http://localhost:8081/bundle/",
//publicPathFill = "/",
getPlugins = function () {
return [
new ExtractTextPlugin("site.css"),
new webpack.HotModuleReplacementPlugin()
]
}
}
module.exports = {
/**
* Entry for all client side code.
* #var {object} entry
*/
entry: entryFill,
plugins: getPlugins(),
output: {
path: publicPathFill,
filename: '[name].js',
libraryTarget: 'umd'
publicPath: 'http://localhost:8081/bundle/'
},
resolve: {
extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"],
//alias: {
// 'react': 'preact-compat',
// 'react-dom': 'preact-compat',
// 'react-router': 'preact-compat'
//}
},
module: {
loaders: [
// { test: /\.(jpe?g|png|gif|svg)$/i, loader: 'url?limit=10000!img?progressive=true'},
{ test: /\.css$/, loader: "style-loader!css-loader" },
{
test: /\.scss$/,
loader: ExtractTextPlugin.extract(
//Need:?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5] to set the right name for each css!
"style",
"css!postcss-loader!sass")
},
// { test: /bootstrap-sass\/assets\/javascripts\//, loader: 'imports?jQuery=jquery' },
{ test: /\.tsx?$/, loader: "ts-loader" },
{
test: /\.(pug|png|ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
loader: 'file-loader'
}
]
},
postcss: function () {
return [autoprefixer(
// { browsers: ['ie 10', 'firefox 20', 'safari 9.1','Chrome ] }
{ browsers: ['> 0%'] }
)];
}
};
and my webpack dev server running in node looks like:
import * as express from 'express';
import * as path from 'path'
let webpack = require('webpack');
let WebpackDevServer = require('webpack-dev-server');
let proxy = require('proxy-middleware');
let url = require('url');
let config = require('../../../webpack.config.js');
import { post } from "./post";
import { pages } from "./pages";
module.exports = function (app: any, passport: any) {
var router = express.Router();
post(router, passport);
pages(router);
if (process.env.NODE_ENV === 'development') {
let base = path.join(__dirname, "..", "..", "assets");
console.log("Base : " + base);
//console.log(__dirname);
let server = new WebpackDevServer(webpack(config), {
//contentBase: base,
hot: true,
quiet: false,
noInfo: false,
publicPath: "/bundle/",
stats: { colors: true }
});
server.listen(8081, "localhost", function () { });
app.use('/bundle', proxy(url.parse('http://localhost:8081/bundle')));
}
app.use('/', router);
};
I serve the html page like so:
import { createStore, bindActionCreators, applyMiddleware } from 'redux';
import { routerReducer } from 'react-router-redux'
import thunkMiddleware from 'redux-thunk'
import * as path from 'path'
import * as fs from "fs";
import { Routes, Navigation, RootReducer } from "../../client/index";
export function pages(router) {
router.get('/*', function (req: any, res: any, next) {
let initial = {};
const store = createStore(RootReducer, initial, applyMiddleware(thunkMiddleware))
const body = ReactDOMServer.renderToString(
<Provider store={store}>
<StaticRouter location={req.url}>
<Routes/>
</StaticRouter>
</Provider>
)
//const nav = ReactDOMServer.renderToString(
// <Navigation/>
//)
const nav = ReactDOMServer.renderToString(
<div> Nav</div>
)
console.log("Body:");
console.log(body);
//const state = store.getState()
const state = {};
res.send(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Clement Cleaning</title>
<meta name="description" content="A cleaning business">
<meta name="author" content="Mortonproductions">
<script src="/bundle/bootstrap.js"></script>
<link rel="stylesheet" href="/bundle/site.css">
</head>
<body>
<header>
<h1>
Clement Cleaning
</h1>
<h2>
A cleaning business based in Loch Lomond
</h2>
</header>
<nav>
<div id="react-nav">${nav}</div>
</nav>
<section>
<div id="react-router">${body}</div>
</section>
<footer>
Progressive web app produced my morton productions
</footer>
<script>window.__REDUX_STATE__ = ${JSON.stringify(state)}</script>
<script src="/bundle/vendor.bundle.js"></script>
<script src="/bundle/index.js"></script>
</body>
</html>`
)
});
}
I was using truffle-webpack-demo but started getting errors since I upgraded to truffle 3.2.1. I found the new branch of the same repo and copied config from there. Which should be working, but now npm start gives me the following error.
Starting the development server...
Failed to compile.
Error in ./contracts/MetaCoin.sol
Module build failed: Error: You must specify the network name to deploy to. (network)
# ./src/components/AccountList/AccountListContainer.js 37:16-49
I have upgraded to the following versions of truffle, webpack-dev-server, webpack and truffle-solidity-loader
truffle-solidity-loader: "git+https://github.com/sogoiii/truffle-solidity-loader.git#1f1e213d52f033b6863218307b8968ae68220fe1"
truffle: 3.2.1
webpack-dev-server: 2.4.1
webpack: 2.2.1
Below is my config/webpack.config.dev.js
var path = require('path')
var autoprefixer = require('autoprefixer')
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var precss = require('precss')
// TODO: hide this behind a flag and eliminate dead code on eject.
// This shouldn't be exposed to the user.
var isInNodeModules = path.basename(path.resolve(path.join(__dirname, '..', '..'))) === 'node_modules'
var relativePath = isInNodeModules ? '../../..' : '..'
var isInDebugMode = process.argv.some(arg =>
arg.indexOf('--debug-template') > -1
)
if (isInDebugMode) {
relativePath = '../template'
}
var srcPath = path.resolve(__dirname, relativePath, 'src')
var nodeModulesPath = path.join(__dirname, '..', 'node_modules')
var indexHtmlPath = path.resolve(__dirname, relativePath, 'index.html')
var faviconPath = path.resolve(__dirname, relativePath, 'favicon.ico')
var buildPath = path.join(__dirname, isInNodeModules ? '../../..' : '..', 'build')
var provided = {
'Web3': 'web3'
}
module.exports = {
devtool: 'eval',
entry: [
require.resolve('webpack-dev-server/client') + '?http://localhost:3000',
require.resolve('webpack/hot/dev-server'),
path.join(srcPath, 'index')
],
output: {
// Next line is not used in dev but WebpackDevServer crashes without it:
path: buildPath,
pathinfo: true,
filename: 'bundle.js',
publicPath: '/'
},
resolve: {
root: srcPath,
extensions: ['', '.js'],
alias: {
contracts: path.resolve('contracts'),
// config: require('../truffle').networks.development,
config: path.resolve('truffle.js')
}
},
module: {
preLoaders: [
{
test: /\.js$/,
loader: 'eslint',
include: srcPath
}
],
loaders: [
{
test: /\.js$/,
include: srcPath,
loader: 'babel',
query: require('./babel.dev')
},
{
test: /\.css$/,
include: srcPath,
loader: 'style!css!postcss'
},
{
test: /\.json$/,
loader: 'json'
},
{
test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2)$/,
loader: 'file'
},
{
test: /\.(mp4|webm)$/,
loader: 'url?limit=10000'
},
{
test: /\.sol/,
loader: 'truffle-solidity',
loaders: ['json-loader', 'truffle-solidity-loader?migrations_directory=' + path.resolve(__dirname, '../migrations') + '&network=development&contracts_build_directory=' + path.resolve(__dirname, '../dist/contracts')]
}
]
},
eslint: {
configFile: path.join(__dirname, 'eslint.js'),
useEslintrc: false
},
postcss: function () {
return [precss, autoprefixer]
},
plugins: [
new HtmlWebpackPlugin({
inject: true,
template: indexHtmlPath,
favicon: faviconPath
}),
new webpack.ProvidePlugin(provided),
new webpack.DefinePlugin({
'myEnv': JSON.stringify(require('../truffle').networks.development),
'process.env.NODE_ENV': '"development"'
}),
new webpack.EnvironmentPlugin(['NODE_ENV']),
new webpack.HotModuleReplacementPlugin()
]
}
Here's my MetaCoin.sol
pragma solidity ^0.4.4;
import "./ConvertLib.sol";
// This is just a simple example of a coin-like contract.
// It is not standards compatible and cannot be expected to talk to other
// coin/token contracts. If you want to create a standards-compliant
// token, see: https://github.com/ConsenSys/Tokens. Cheers!
contract MetaCoin {
mapping (address => uint) balances;
event Transfer(address indexed _from, address indexed _to, uint256 _value);
function MetaCoin() {
balances[tx.origin] = 6000000000;
}
function sendCoin(address receiver, uint amount) returns(bool sufficient) {
if (balances[msg.sender] < amount) return false;
balances[msg.sender] -= amount;
balances[receiver] += amount;
Transfer(msg.sender, receiver, amount);
return true;
}
function getBalanceInEth(address addr) returns(uint){
return convert(getBalance(addr),2);
}
function getBalance(address addr) returns(uint) {
return balances[addr];
}
function convert(uint amount,uint conversionRate) returns (uint convertedAmount)
{
return amount * conversionRate;
}
}
Here's my AccountListContainer.js
import React, { Component } from 'react'
import AccountList from 'components/AccountList/AccountList'
import SendCoin from 'components/SendCoin/SendCoin'
import Contract from 'truffle-contract'
import MetaCoinArtifact from 'contracts/MetaCoin.sol';
const MetaCoin = Contract(MetaCoinArtifact)
import Web3 from 'web3';
const provider = new Web3.providers.HttpProvider('http://localhost:8545')
MetaCoin.setProvider(provider);
class AccountListContainer extends Component {
constructor(props) {
super(props)
this.state = {
accounts: [],
coinbase: ''
}
this._getAccountBalance = this._getAccountBalance.bind(this)
this._getAccountBalances = this._getAccountBalances.bind(this)
}
_getAccountBalance (account) {
return MetaCoin.deployed()
.then(meta => {
return meta.getBalance.call(account, {from: account})
})
.then(function (value) {
return { account: value.valueOf() }
})
.catch(function (e) {
console.log(e)
throw e
})
}
_getAccountBalances () {
this.props.web3.eth.getAccounts(function (err, accs) {
if (err != null) {
window.alert('There was an error fetching your accounts.')
console.error(err)
return
}
if (accs.length === 0) {
window.alert("Couldn't get any accounts! Make sure your Ethereum client is configured correctly.")
return
}
this.setState({coinbase: accs[0]})
var accountsAndBalances = accs.map((account) => {
return this._getAccountBalance(account).then((balance) => { return { account, balance } })
})
Promise.all(accountsAndBalances).then((accountsAndBalances) => {
this.setState({accounts: accountsAndBalances, coinbaseAccount: accountsAndBalances[0]})
})
}.bind(this))
}
componentDidMount() {
const refreshBalances = () => {
this._getAccountBalances()
}
refreshBalances()
setInterval(()=>{
refreshBalances();
return refreshBalances
}, 5000)
}
render() {
return (
<div>
<AccountList accounts={this.state.accounts} />
<SendCoin sender={this.state.coinbase} />
</div>
)
}
}
export default AccountListContainer
Here's the truffle.js
module.exports = {
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*"
},
staging: {
host: "localhost",
port: 8546,
network_id: 1337
}
}
};
Given the error Module build failed: Error: You must specify the network name to deploy to. (network), I suspect your truffle.js file has not been updated to the new truffle 3 spec.
Review how the config changed at the migration guided. It should look something like.
module.exports = {
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*"
},
staging: {
host: "localhost",
port: 8546,
network_id: 1337
},
ropsten: {
host: "158.253.8.12",
port: 8545,
network_id: 3
}
}
};