Reputation: 2609
I have added Angular Universal to an existing project with i18n. I can build it but I am not able to serve it.
At the moment I get this error:
Cannot find module '/home/my-user/my-app/dist/da/server'
The structure of the files in "dist" folder is:
dist
|-da
|-browser
|-server
|-main.js
|-en
|-browser
|-server
|-main.js
|-server.js
My serve scripts in package.json:
"serve-da:ssr": "node dist/da/server",
"serve-en:ssr": "node dist/en/server",
This indicates to me that server.js
is supposed to be located inside "da" and "en" folders and not in the root of the dist folder? If yes, where do I correct this? I am not sure where and how server.js is generated.
I am not even sure that the error is about the server.js file. I am just assuming... I hope some can point me in the right direction...
These are my config files:
angular.json
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"edApp": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "ed",
"schematics": {
"@schematics/angular:component": {
"styleext": "scss"
}
},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/browser",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets",
"src/manifest.json",
"src/apple-touch-icon.png",
"src/browserconfig.xml",
"src/favicon-16x16.png",
"src/favicon-32x32.png",
"src/mstile-150x150.png",
"src/safari-pinned-tab.svg"
],
"scripts": [],
"styles": [
{
"input": "./node_modules/bootstrap/dist/css/bootstrap.css"
},
"./node_modules/ngx-bootstrap/datepicker/bs-datepicker.css",
"src/styles.scss"
]
},
"configurations": {
"da": {
"aot": true,
"outputPath": "dist/da/browser",
"baseHref": "/da/",
"i18nFile": "src/i18n/messages.da.xlf",
"i18nLocale": "da",
"i18nFormat": "xlf",
"i18nMissingTranslation": "[Fejl]"
},
"en": {
"aot": true,
"outputPath": "dist/en/browser",
"baseHref": "/en/",
"i18nFile": "src/i18n/messages.en.xlf",
"i18nLocale": "en",
"i18nFormat": "xlf",
"i18nMissingTranslation": "[Error]"
},
"production-da": {
"index": {
"input": "src/index.prod.da.html",
"output": "index.html"
},
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputPath": "dist/da/browser",
"baseHref": "/da/",
"i18nFile": "src/i18n/messages.da.xlf",
"i18nLocale": "da",
"i18nFormat": "xlf",
"i18nMissingTranslation": "[Fejl]",
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"serviceWorker": true
},
"production-en": {
"index": {
"input": "src/index.prod.en.html",
"output": "index.html"
},
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputPath": "dist/en/browser",
"baseHref": "/en/",
"i18nFile": "src/i18n/messages.en.xlf",
"i18nLocale": "en",
"i18nFormat": "xlf",
"i18nMissingTranslation": "[Error]",
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"serviceWorker": true
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "edApp:build",
"proxyConfig": "proxy.conf.json",
"disableHostCheck": true
},
"configurations": {
"production-da": {
"browserTarget": "edApp:build:production-da"
},
"production-en": {
"browserTarget": "edApp:build:production-en"
},
"en": {
"browserTarget": "edApp:build:en"
},
"da": {
"browserTarget": "edApp:build:da"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "edApp:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"styles": [
"src/styles.scss"
],
"scripts": [],
"assets": [
"src/favicon.ico",
"src/assets",
"src/manifest.json"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"server": {
"builder": "@angular-devkit/build-angular:server",
"options": {
"outputPath": "dist/server",
"main": "src/main.server.ts",
"tsConfig": "src/tsconfig.server.json"
},
"configurations": {
"production-da": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputPath": "dist/da/server",
"i18nFile": "src/i18n/messages.da.xlf",
"i18nFormat": "xlf",
"i18nLocale": "da",
"sourceMap": false,
"optimization": {
"scripts": false,
"styles": true
}
},
"production-en": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputPath": "dist/en/server",
"i18nFile": "src/i18n/messages.en.xlf",
"i18nFormat": "xlf",
"i18nLocale": "en",
"sourceMap": false,
"optimization": {
"scripts": false,
"styles": true
}
}
}
}
}
},
"edApp-e2e": {
"root": "e2e/",
"projectType": "application",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "edApp:serve"
},
"configurations": {
"production": {
"devServerTarget": "edApp:serve:production"
}
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": "e2e/tsconfig.e2e.json",
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "edApp"
}
package.json scripts section
I am using build:ssr and serve-da:ssr
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"before-build": "node prebuild.js",
"extract-i18n": "ng xi18n --output-path i18n --i18n-locale en && xliffmerge --profile xliffmerge.json",
"serve-da": "ng serve -o --configuration=da",
"serve-en": "ng serve -o --configuration=en",
"serve-da-home": "ng serve -o --configuration=da --host 0.0.0.0",
"build-da": "ng build --configuration=da",
"build-en": "ng build --configuration=en",
"prebuild-prod-da": "npm run before-build",
"build-prod-da": "ng build --prod --configuration=production-da",
"build-prod-en": "ng build --prod --configuration=production-en",
"build-all": "for lang in da en; do npm run build-$lang; done",
"build-prod-all": "for lang in da en; do npm run build-prod-$lang; done",
"compile:server": "webpack --config webpack.server.config.js --progress --colors",
"serve-da:ssr": "node dist/da/server",
"serve-en:ssr": "node dist/en/server",
"build:ssr": "npm run build:client-and-server-bundles && npm run compile:server",
"build:client-and-server-bundles": "npm run build-prod-all && ng run edApp:server:production-da --bundleDependencies all && ng run edApp:server:production-en --bundleDependencies all"
server.ts
import 'zone.js/dist/zone-node';
import * as express from 'express';
import {join} from 'path';
//Allow access the window or document globals without causing any errors.
//Especially useful when using 3rd party modules
const domino = require('domino');
const fs = require('fs');
const path = require('path');
// Use the browser index.html as template for the mock window
const template = fs.readFileSync(path.join(__dirname, '.', 'dist', 'index.html')).toString();
// Shim for the global window and document objects.
const window = domino.createWindow(template);
global['window'] = window;
global['document'] = window.document;
// Express server
const app = express();
const PORT = process.env.PORT || 4000;
//const DIST_FOLDER = join(process.cwd(), 'dist/browser');
const DIST_FOLDER = join(process.cwd(), 'dist');
const routes = [
{path: '/da/*', view: 'da/index', bundle: require('./dist/da/server/main')},
{path: '/en/*', view: 'en/index', bundle: require('./dist/en/server/main')}
];
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const {AppServerModuleNgFactory, LAZY_MODULE_MAP, ngExpressEngine, provideModuleMap} = require('./dist/server/main');
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
app.set('view engine', 'html');
//app.set('views', DIST_FOLDER);
app.set('views', join(DIST_FOLDER, 'browser'));
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));
routes.forEach((route) => {
app.get(route.path, (req, res) => {
res.render(route.view, {
req, res, engine: ngExpressEngine({
bootstrap: route.bundle.AppServerModuleNgFactory,
providers: [provideModuleMap(route.bundle.LAZY_MODULE_MAP)]
})
});
});
});
// Start up the Node server
app.listen(PORT, () => {
console.log(`Node Express server listening on http://localhost:${PORT}`);
});
webpack.server.config.js
// Work around for https://github.com/angular/angular-cli/issues/7200
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode: 'none',
entry: {
// This is our Express server for Dynamic universal
server: './server.ts'
},
externals: {
'./dist/server/main': 'require("./server/main")'
},
target: 'node',
resolve: { extensions: ['.ts', '.js'] },
optimization: {
minimize: false
},
output: {
// Puts the output at the root of the dist folder
path: path.join(__dirname, 'dist'),
filename: '[name].js'
},
module: {
noParse: /polyfills-.*\.js/,
rules: [
{ test: /\.ts$/, loader: 'ts-loader' },
{
// Mark files inside `@angular/core` as using SystemJS style dynamic imports.
// Removing this will cause deprecation warnings to appear.
test: /(\\|\/)@angular(\\|\/)core(\\|\/).+\.js$/,
parser: { system: true },
},
]
},
plugins: [
new webpack.ContextReplacementPlugin(
// fixes WARNING Critical dependency: the request of a dependency is an expression
/(.+)?angular(\\|\/)core(.+)?/,
path.join(__dirname, 'src'), // location of your src
{} // a map of your routes
),
new webpack.ContextReplacementPlugin(
// fixes WARNING Critical dependency: the request of a dependency is an expression
/(.+)?express(\\|\/)(.+)?/,
path.join(__dirname, 'src'),
{}
)
]
};
Upvotes: 0
Views: 1110
Reputation: 2609
What @David said is absolutely right... No need for two servers.
I have been able to get past this error, with the following changes:
Choose which language is the default, and then set outputPath
for that build to just dist/browser
in angular.json.
In server.ts set that same language to default in the routes:
const routes = [
{path: '/en/*', view: 'en/index', bundle: require('./dist/en/server/main')},
{path: '/*', view: 'da/index', bundle: require('./dist/da/server/main')}
];
In server.ts I also corrected this line (__dirname
removed):
const template = fs.readFileSync(path.join('.', 'dist','browser','index.html')).toString();
I am now dealing with a set of brand new errors - browser specific stuff that I need to mock up somehow. Lucky me ;-)
Upvotes: 0
Reputation: 34475
Do you really want to run two instances of the server.js
script?
Only one instance of the server.js
script is generated. You can run only one instance of the server script and dynamically load the main bundle, like you already do. In this case, just modify the run script:
"serve:ssr": "node dist/server",
If you really want to run 2 instances, you need to modify the serve script to change the port if you are running them both on the same machine (but you can keep the same generated server.js file)
"serve-da:ssr": "env PORT=4000 node dist/server",
"serve-en:ssr": "env PORT=4001 node dist/server",
Upvotes: 1