angular-cli: Universal server bundle is not working properly

Bug Report or Feature Request (mark with an x)

- [x] bug report -> please search issues before submitting
- [ ] feature request

Versions.

@angular/cli: 1.3.0-rc.1 node: 6.10.3 os: darwin x64

Repro steps.

Follow these steps here:

https://github.com/angular/angular-cli/wiki/stories-universal-rendering

Add https://github.com/ngx-translate/core to the app.

Build a server bundle.

Test it and you will see the error below.

The log given by the failure.

<some-excluded-path>/ng-boilerplate/node_modules/@ngx-translate/core/src/translate.store.js:1
(function (exports, require, module, __filename, __dirname) { import { EventEmitter } from "@angular/core";
                                                              ^^^^^^
SyntaxError: Unexpected token import
    at createScript (vm.js:56:10)
    at Object.runInThisContext (vm.js:97:10)
    at Module._compile (module.js:542:28)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
    at Object.17 (<some-excluded-path>/ng-boilerplate/dist-server/main.75d7fa7aeab5f9b24f61.bundle.js:1:6560)
    at e (<some-excluded-path>/ng-boilerplate/dist-server/main.75d7fa7aeab5f9b24f61.bundle.js:1:149)
    at Object.2cGb (<some-excluded-path>/chrillewoodz/ng-boilerplate/dist-server/main.75d7fa7aeab5f9b24f61.bundle.js:1:7952)
    at e (<some-excluded-path>/ng-boilerplate/dist-server/main.75d7fa7aeab5f9b24f61.bundle.js:1:149)
    at Object.Zq8w (<some-excluded-path>/ng-boilerplate/dist-server/main.75d7fa7aeab5f9b24f61.bundle.js:1:28947)
    at e (<some-excluded-path>/ng-boilerplate/dist-server/main.75d7fa7aeab5f9b24f61.bundle.js:1:149)

Desired functionality.

Basically the issue here is that node is resolving the wrong module. It’s looking inside of the node_modules folder instead of in the vendor file in the server bundle.

Mention any other details that might be useful.

You can download a project with these steps already done:

https://github.com/chrillewoodz/ng-boilerplate/tree/universal

So simply run npm run universal and you will see the error.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 37
  • Comments: 80 (19 by maintainers)

Most upvoted comments

I tried workaround mentioned by @Toanzzz and quickly ran into this exception: reflect-metadata shim is required when using class decorators I tried importing the reflect-metadata package which seemed to help with this issue, but I quickly ran into another one: Cannot read property 'subscribe' of undefined. I also tried the solution supplied by @harshes53, but this one didn’t work at all to me and once again I got syntaxt error on the import token. At this point I called it quits - this issue is quite frustrating and I was even considering dropping use of angular-cli for the sake of some seed project which had universal support built-in. I’m still evaluating Angular 4.x and CLI, so I’m not working under the time pressure, but having such issues with integration of 3rd party libraries is a major concern to me at this point. Anyone has some other ideas how to resolve this?

More investigation!

So I think I have something more definitive this time. I noticed that a lot of the packages had UMD bundles, however for some reason they were not being resolved into the Universal application. TLDR: libs that don’t produce a FESM build casue cli to produce deep imports where the umd bundle cannot be resolved.

I made a sample repository here showing casing 2 libs. lib1 - a properly formatted lib which can work with universal without problem lib2 - the average kind of lib that you see which runs into Universal issues

The big difference between them is that lib2 will produce a build output where the JS files that use import/export are not concatenated together or in other words are not flat es modules, lib1 does produce a flat esm build.

If you run the demo with the command below (sorry about all the dependency installs) then you’ll notice that lib2 throws the error we’ve seen so much in this thread.

SyntaxError: Unexpected token import

Well if you take a look at app/dist/dist-server/main.bundle.js and a ctrl + f you’ll see that there have 2 entries: require("lib2/src/my-module") and require("lib1") AHA! Now we’re getting somewhere. So we know that require("lib1") will resolve to the umd bundle but a deep import like require("lib2/src/my-module") has no option to be to resolve the es module, therefore casuing our issue!

What can be done?

  • As I stated above the best way to fix this issue is for library owners to distribute packages in the correct format, which as I has discovered includes a Flat ES Module option.
  • There may be something the CLI can do to avoid producing these deep imports but I have no knowledge of that side of their build system. @hansl Any idea if something can be done?

https://github.com/Toxicable/universal-deep-import

cd lib1 && yarn && cd ../lib2 && yarn && cd ../app && yarn && npm run build && npm run build:static

@intellix No, I gave up on SSR with angular. Went with prerendering approach (instead of SSR), used Puppeteer to go through all URLs, render the page, save resulting html file, then served html files as static. Drawback is that prerendering is significantly slower (even in multi-threaded environment).

For future project I’ll consider a framework (not Angular) that has SSR fixed (e.g React/Next.js, or stenciljs, or VueJS).

Is there any resolution on this issue yet? Or an ETA? Neither the require('import-export') or the webpack workarounds suggested above have worked for me; the first throws errors when using the @ng-bootstrap datepicker module (Unexpected token export from the npm), and the second throws various @NgModule annotation errors that I can’t seem to work around at all (I’ve played whack-a-mole with them for hours now).

We really need a way to bundle all node_modules for server-side rendering.

We are working on a fix with the libraries themselves. In the meantime, you can use (this is not a supported solution, just a fix) something like https://www.npmjs.com/package/import-export. Alternatively, you can disable AOT in the meantime.

I’m keeping this issue open in the meantime for keeping the discussion.

Here is the workaround that I’m currently using in my project: recompile the server output with webpack so that other 3rd party libs got transpile to commonjs for node to read

  1. Add webpack to devDependencies: npm i webpack --save-dev
  2. Add minimal webpack config
// webpack.config.js

const hash = getHash();
const input = `./dist-server/main.${hash}.bundle`;
const output = `./dist-server/main.repack.${hash}.bundle.js`;

module.exports = {
  target: 'node',
  entry: input,
  output: { filename: output, libraryTarget: 'commonjs' },
}

function getHash() {
  const fs = require('fs');
  const path = require('path');

  const files = fs.readdirSync(`${process.cwd()}/dist-server`);
  const mainFiles = files.filter(file => file.startsWith('main'));
  return mainFiles[0].split('.')[1];
}
  1. Target the new compiled file
// server.ts

//const AppServerModuleNgFactory = require(`./dist-server/main.${hash}.bundle`).AppServerModuleNgFactory;
const AppServerModuleNgFactory = require(`./dist-server/main.repack.${hash}.bundle`).AppServerModuleNgFactory;
  1. Append webpack build step after server build
// package.json

//"universal": "ng build --prod && ng build --prod --app 1 && ts-node server"
"universal": "ng build --prod && ng build --prod --app 1 && webpack -p && ts-node server"

Hope it help

Even when I do my build with the ‘–no-aot’ flag, I still get the same error.

> ng build --prod --no-aot && ngc

Hash: 3527298f2a176234eb92                                                              
Time: 15948ms
chunk    {0} polyfills.550cf10c9aa54b8194c7.bundle.js (polyfills) 177 kB {4} [initial] [rendered]
chunk    {1} main.dc72226c09f60d9253bd.bundle.js (main) 79.7 kB {3} [initial] [rendered]
chunk    {2} styles.d41d8cd98f00b204e980.bundle.css (styles) 69 bytes {4} [initial] [rendered]
chunk    {3} vendor.68fbe47955f62acf4ee4.bundle.js (vendor) 2.56 MB [initial] [rendered]
chunk    {4} inline.42fcf4018d66c4aef552.bundle.js (inline) 0 bytes [entry] [rendered]

> ts-node src/server.ts

/Documents/code/universal-demo/node_modules/ngx-facebook/dist/esm/providers/facebook.js:1
(function (exports, require, module, __filename, __dirname) { import { Injectable } from '@angular/core';
                                                              ^^^^^^
SyntaxError: Unexpected token import

Has anyone else managed to work around this problem? I’d appreciate any help as this is currently completely blocking my plans to go live with my changes.

I’m seeing the same with ng-bootstrap. Any ideas how to fix this? I’m currently investigating using universal for our large project and the issue with integrating 3rd party libraries puts this work on hold.

Just tried to use import-export, it passes the error place but gives another error:

/node_modules/@angular/compiler/bundles/compiler.umd.js:19601
                ("ort * as " + prefix + " = require("" + importedModuleName + "");"));
                                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^

SyntaxError: Unexpected string

We’ve had the same issue with ng2-page-scroll. In that case, they have a umd version in a ‘bundles’ subfolder within the node module.

We wrote a simple node script to check main.bundle.js (created when we build for the server) for references to versions of ng2-page-scroll and replace them with the umd version.

This seems to fix the “unexpected token import” error at least- though I’m not sure if it actually works for the end result, as we have many more errors even after getting past this one. Could be worth a try.

The script we used is in this gist for reference: https://gist.github.com/J2D2Development/6f520dd991a6a33c1152aabcdf346790

for @MarkPieszak and anyone interested in using universal along with i18n translations in aot mode, as when compiling the server.ts to server.js it does not work (since the compilation adds the html strings into the resulting server.js file) here is how i manged to implement it:

  1. first, in order to handle the unexpected token issue mentioned here earlier, i am using the solution @AnthonyNahas wrote here: SebastianM/angular-google-maps#1052 . this solution is specific to the problematic package and therfore does not require to compile the server.ts to server.js

  2. regarding compilation, i am compiling the server packaged and browser pacakge the same as in the universal starter cli exmaple however i am doing it for every language while adding each language compilation into its own direcotry. the resulting structure is as follows: image as you can see, i am also copying the assest folder into the dist folder (i am doing this with script off-course)

  3. the last step after i have everything compiled is to run the node server using the server.js file with the following server.js file:

//@formatter:off
// the above line tells the lint to not change the file
require('zone.js/dist/zone-node');
require('reflect-metadata');
const express = require('express');
const fs = require('fs');

const core = require('@angular/core');

const { platformServer, renderModuleFactory } = require('@angular/platform-server');
const { ngExpressEngine } = require('@nguniversal/express-engine');
// Import module map for lazy loading
const { provideModuleMap } = require('@nguniversal/module-map-ngfactory-loader');

// Import the AOT compiled factory for your AppServerModule.
// This import will change with the hash of your built server bundle.
const serverEn = require('./dist/en/server/main.bundle');
const serverFr = require('./dist/fr/server/main.bundle');
const serverEs = require('./dist/es/server/main.bundle');

// Faster server renders w/ Prod mode (dev mode never needed)
core.enableProdMode();

const app = express();

// Set the engine
app.engine('html', ngExpressEngine({

}));

app.set('view engine', 'html');

app.set('views', './dist/');

app.get(/^\/fr((\/(.*)))?$/, (req, res) => {
  res.render('./fr/browser/app-root', {
  req,
  res,
  bootstrap: serverFr.AppServerModuleNgFactory,
  providers: [
    provideModuleMap(serverFr.LAZY_MODULE_MAP)
  ]
});
})

app.get(/^\/es((\/(.*)))?$/, (req, res) => {
  res.render('./es/browser/app-root', {
  req,
  res,
  bootstrap: serverEs.AppServerModuleNgFactory,
  providers: [
    provideModuleMap(serverEs.LAZY_MODULE_MAP)
  ]
});
})


app.get(/\/(.*)/, (req, res) => {
  res.render('./en/browser/app-root', {
  req,
  res,
  bootstrap: serverEn.AppServerModuleNgFactory,
  providers: [
    provideModuleMap(serverEn.LAZY_MODULE_MAP)
  ]
});
})

app.listen(process.env.PORT || 3000, () => {
  console.log(`Listening at ${process.env.PORT || 3000}`);
})

regarding all the static files, i am handling them as part of my hosting however you can also add them to the server.js file so that the node server will handle them.

Good Luck!

One workaround for now is to basically make your server.js file a TypeScript file, and have Webpack setup to process /.ts$/ included your externals, which are things like node_modules / etc.

Take a look at the comment I made here for more info: https://github.com/angular/universal-starter/issues/428#issuecomment-331558400

Hopefully we can have this as an example in the universal-starter CLI demo as well soon.

We solved this issue by transforming ngx-translate with babel with a pre-script in our package.json.

Eg. package.json

...
"preserve:yourproject": "node node_modules/babel-cli/bin/babel.js node_modules/@ngx-translate/core --out-dir node_modules/@ngx-translate/core --presets es2015"
...

And the then in your components/services/whatver you can simply:

import { TranslateService } from '@ngx-translate/core';

At the moment no guidelines were provided regarding the “packaging format” making a lib working with Angular Universal. If someone could write these guidelines, I would be happy to help filling these guidelines and provide PRs to some libs to move things along. Most of Angular application needs Universal working to go live, we need to fill this SSR gap as soon as possible. @Toxicable do you have at least some bullet points to provide as you deeply investigate the problem? Thank you for your clear explanations by the way

Same issue. @hansl any updates or ETA? @AnthonyNahas’s answer is a good work around

I was having the issue where 3rd party dependencies (like zone.js) weren’t getting included (the main.bundle.js was using a require statement to grab it from node_modules which isn’t on our prod environment) I was able to resolve it by adding the --bundle-dependencies=all argument to my ng build command. For Angular 6 you would need to add the "bundleDependencies": "all" property to the configurations property in the angular.json file. Not sure if that helps anyone else, just thought I’d share it as I didn’t see mention of it here.

Thanks for looking into this Toxicable

Are you saying that there is no intention for the angular/cli team to allow packaging of 3rd party node_modules imported into a project to run using Angular Universal and that library maintainers will need to convert all packages to commonjs format for distribution in order for them to be used in server side rendering? Because that’s a pretty big ask of the vendors, and will, for a team like ours who are using multiple vendor packages, mean we won’t be able to go live with our Angular Universal project for potentially months whilst the 3rd parties sort this out

same issue with here with ng-bootstrap

@Toanzzz I’m going to try your workaround now. One comment: by using --output-hashing=none you can avoid getHash() function, cause bundle name is always main.bundle.js.

Just tried, I get the same error (the first one).

So you know, there was the exact same error with @angular/flex-layout beta 8, but with the last builds, it’s gone, because they updated to the official way Angular modules are build. One main difference is that there is now a es2015 entry point in their package.json, but I don’t know if it’s what solved the problem, or a change in their build config.

One point which surprised me first is that the server bundle only contains the app code. So Angular packages (@angular/core and so on) and all other packages need to be installed on the server project too. And the error seems to show that the server bundle requests the packages from node_modules in the wrong format (it’s es2015, while it should be commonjs).

Shouldn’t the CLI produce a vendor bundle too, to be sure everything is here in the good format ?

yeah, I’m still waiting for universal working smooooothly 😢

@dannyrevenant Please don’t twist what i’ve said into your own opinion, No that is not what I’m saying.

My words are only of my own opinion, I do not speak on behalf of any groups, I am just a regular dev that enjoys contributing to Angular and surrounding communities, most notably Universal as of recent.

As for what the CLI team can do; The CLI does support importing modules from npm into a Universal project, take Material for example, that works with Universal fine since it’s correctly packaged, exemplifying that this isn’t an issue with the CLI, instead an issue with the 3rd parties packaging format. However I believe there is some webpack magic that they might be able to configure to allow for importing incorrectly formatted packages but it’s not obvious, simple or a good solution, I did not investigate this route since I was not looking for a solution by changing webpack config.

For your mention on packaging formats; no I am not suggesting that libs package their packages solely for Universal, if you would take some time to lookup how libs should be distributing packages then you’d find that you can and should distribute your package in multiple formats so that different formats can be correctly resolved for different situations.

The best thing you can do to getting a resolution to this issue is ask maintainers of the projects with incorrect packaging formats to correct it, or better yet, whip up a PR youreself, I’m sure they’ll appreciate it.

I’ve been doing a bit of investigation on this the last few days trying a few solutions but havn’t come up with anything that’ll do it easily.

Just to reiterate the actual issue here; The Library being used is for packaged/formatted correctly. For Universal to be able to use a node package is must be commonjs formatted, meaning that it uses require not import since node does not currently support esm modules (import/export). Which means this is not an issue with the CLI, it’s an issue with the libraries not packaging their modules correctly.

Work around i’ve tried: require(‘import-export’)

  • This module just dosen’t support all the right syntax for to be useful here.

Node nightly build

re-packaging libs

  • I had some success with this: I was able to use tsc to override libs into the correct commonjs format and successfully used that lib in a Universal project. However this is not a good solution at all, it worked for the lib I was testing on but I don’t know if it will work for all, and it’s a pretty ugly lots of scripts

From my investigations my conclusion is that the best way to work around this is getting libraries to correctly format their packages so that they can be consumed in a node environment without issue

Just tryied @Toanzzz and @harshes53 solutions and I finally received this exception:

$. /node_modules/.bin/ts-node server

<some-excluded-path>\node_modules\zone.js\dist\zone-node.js:365
                callback.apply(applyThis, applyArgs);
                         ^
TypeError: Cannot read property 'create' of undefined
    at <some-excluded-path>\packages\core\src\application_ref.ts:365:1
    at ZoneDelegate.invoke (<some-excluded-path>\node_modules\zone.js\dist\zone-node.js:365:26)
    at Object.onInvoke (<some-excluded-path>\node_modules\@angular\core\bundles\core.umd.js:3922:33)
    at ZoneDelegate.invoke (<some-excluded-path>\node_modules\zone.js\dist\zone-node.js:364:32)
    at Zone.run (<some-excluded-path>\node_modules\zone.js\dist\zone-node.js:125:43)
    at NgZone.run (<some-excluded-path>\node_modules\@angular\core\bundles\core.umd.js:3853:69)
    at PlatformRef_._bootstrapModuleFactoryWithZone (<some-excluded-path>\packages\core\src\application_ref.ts:363:1)
    at PlatformRef_.bootstrapModuleFactory (<some-excluded-path>\node_modules\@angular\core\bundles\core.umd.js:4509:21)
    at renderModuleFactory (<some-excluded-path>\node_modules\@angular\platform-server\bundles\platform-server.umd.js:2405:39)
    at Object.<anonymous> (<some-excluded-path>\server.ts:16:1)
    at Module._compile (module.js:570:32)
    at Module.m._compile (<some-excluded-path>\node_modules\ts-node\src\index.ts:392:23)
    at Module._extensions..js (module.js:579:10)
    at Object.require.extensions.(anonymous function) [as .ts] (<some-excluded-path>\node_modules\ts-node\src\index.ts:395:12)
    at Module.load (module.js:487:32)

Sounds great 😃 Any early estimate on when this could be resolved? Just so I can plan ahead a bit.

If it can help, I did a blog post with full explanations.

First it will help you about the hash (no need of it), and second the first thing I would consider is to stick to node. ts-node is a great tool, but in this case it adds more complexity to an already complex configuration, you can do the same with just node.

@MarkPieszak thanks you for your suggestion, I’m successful at creating webpack config. That so cool 😃

Can you update to the latest universal-starter? Using a server.ts file gives you the ability to have your own webpack file (when making that server bundle) so you’ll be all set 😀 @hiepxanh

Hey. @MarkPieszak solution works fine (as seen in universal starter) but it has one problem. if you are working with angular i18 for translation and aot, the translations are part of the compiled html. in @MarkPieszak solution, the server.js output file includes all the htmls inside and therefore makes it impossible to create few packages for different language. @AnthonyNahas solution solves the problem specifically for each package during the package download time and by that allows working with i18 inside universal. you can check the solution here: https://github.com/SebastianM/angular-google-maps/issues/1052 Thanks Anthony

Not to mention a lot of third party authors are lazy and don’t care. So it would require a pull request from an outsider and that is an even bigger ask.

same issue with ng-cookies. hello ,i followed your advice ,but not working @harshes53 externals: [nodeExternals({whitelist: [/ngx-cookie/]})],

@kirillgroshkov did you ever manage to fix the issue that you get after installing import-export? Getting the same thing about the unexpected string regarding:

/node_modules/@angular/compiler/bundles/compiler.umd.js:19810

("ort * as " + prefix + " = require("" + importedModuleName + "");"));

Interestingly, this is the code that it’s complaining about:

converter.importsWithPrefixes.forEach(function (prefix, importedModuleName) {
    // Note: can't write the real word for import as it screws up system.js auto detection...
    preambleLines.push("imp" +
        ("ort * as " + prefix + " from '" + importedModuleName + "';"));
});

i fix similar issue with another npm module (@ngrx). take a look to my solution (only as work around)

#581

if you are using webpack, just whitelist the package causing import syntax error using webpack’s externals property. Let ts-loader compile the package… not the best workaround but it works for me! I had the same issue when using ngx-cookie package. seems like these packages are using ES6 modules syntax while Node.js still uses common.js module syntax.

e.g:

const webpackNodeExternals = require('webpack-node-externals');
...
externals: [webpackNodeExternals({ whitelist: [/ngx-cookie/] })],
... 

Hope it helps…

There, I’ve pushed the latest changes. Now you should be able to find a "universal": "ng build --prod && ng build --prod --app 1 && ts-node server", script in the package json and also the cli at the latest rc.3 release.