Ng-lazyload-image: Angular Universal build prerender error

Created on 8 Jan 2019  ·  23Comments  ·  Source: tjoskar/ng-lazyload-image

  • I'm submitting a ...

    • [x] bug report
    • [ ] feature request
    • [ ] support request
  • Do you want to request a feature or report a bug?
    Bug with your library and Angular Universal build prerender functionality

  • What is the current behavior?
    When you run the command:
    npm run build:prerender

it runs:
"generate:prerender": "cd dist && node prerender",

Then fails with the error:
```/node_modules/ng-lazyload-image/src/lazyload-image.module.js:1
(function (exports, require, module, __filename, __dirname) { import { NgModule } from '@angular/core';
^^^^^^

SyntaxError: Unexpected token import
at createScript (vm.js:80:10)
at Object.runInThisContext (vm.js:139:10)
at Module._compile (module.js:617:28)
at Object.Module._extensions..js (module.js:664:10)
at Module.load (module.js:566:32)
at tryModuleLoad (module.js:506:12)
at Function.Module._load (module.js:498:3)
at Module.require (module.js:597:17)
at require (internal/module.js:11:18)
at Object.ng-lazyload-image/src/lazyload-image.module (/dist/server/main.js:1350:18)


* **provide the steps to reproduce**
1) Duplicate the Angular Universal starter project from:
https://github.com/angular/universal-starter

2) Add your library, following install instructions:
https://github.com/tjoskar/ng-lazyload-image

3) Run the Angular build command to see the error:
```npm run build:prerender```

* **What is the expected behavior?**
No error, and to continue building.

* **What is the motivation / use case for changing the behavior?**
Otherwise your plugin cannot be used with Angular Universal, which means no static site generation :(

* **Please tell us about your environment:**
  - MacOS 10.13.6
  - node 8.9.1
  - ng-cli 6.0.0 and tested with 7.1.4
  - angular 6.0.0 and tested with 7.1.4
  - nguniversal 6.0.0 and tested with 7.0.2

* **Other information**

Looks like other people have had similar problems with Angular Universal and third-party libraries such as yours:
https://github.com/angular/angular-cli/issues/7200#issuecomment-329711848

They say the third-party libraries aren't being built correctly, which means Angular Universal fails:
https://github.com/angular/angular-cli/issues/7200#issuecomment-328991769

for example they suggest adding to your package.json
```"main": "./bundles/quickstart-lib.umd.js",
"module": "./quickstart-lib.es5.js",
"es2015": "./quickstart-lib.js",
"typings": "./quickstart-lib.d.ts",

Approach 1
Patch your plugin root:
npm install @babel/cli @babel/core @babel/preset-env @babel/plugin-transform-modules-umd

Adding a .babelrc file in the root of your plugin folder:

{
  "plugins": [["@babel/plugin-proposal-decorators", { "decoratorsBeforeExport": true }]],
  "presets": ["@babel/preset-env"]
}

Updating your plugins package.json

"main": "./lib-umd/index.js",
"module": "./lib-es5/index.js",
"es2015": "./lib/index.js",
"typings": "./lib/index.d.ts",
"scripts": {
  "build:umd": "babel ./lib/*.js --out-dir ./lib-umd --plugins @babel/plugin-transform-modules-umd",
  "build:es5": "babel ./lib/*.js --out-dir ./lib-es5"
}

Then running the build:
npm run build:es5 && npm run build:umd

And adding to my own project tsconfig.json

"compilerOptions": {
  "paths": { "@angular/*": ["../node_modules/@angular/*"] },
}

But still getting the same error with Angular Universal :(

Approach 2
Use the Typescript build options for the example project at:
https://github.com/filipesilva/angular-quickstart-lib

bug

Most helpful comment

I experience the same issue while trying to build with angular universal. @tjoskar have you thought about using ng-packagr to build your library? You could build and bundle your library in FESM2015, FESM5, and UMD formats.
Have a look here: https://github.com/ng-packagr/ng-packaged/blob/master/package.json#L11 (example project using ng-packagr).

All 23 comments

I also got the same error, I have angular universal installed

``` node_modules\ng-lazyload-image\index.js:1
(function (exports, require, module, __filename, __dirname) { import { LazyLoadImageDirective } from './src/lazyload-image.directive';
^^^^^^

SyntaxError: Unexpected token import
at createScript (vm.js:80:10)
at Object.runInThisContext (vm.js:139:10)
at Module._compile (module.js:617:28)
at Object.Module._extensions..js (module.js:664:10)
at Module.load (module.js:566:32)
at tryModuleLoad (module.js:506:12)
at Function.Module._load (module.js:498:3)
at Module.require (module.js:597:17)
at require (internal/module.js:11:18)
at eval (webpack:///external_%22ng-lazyload-image%22?:1:18) ```

I ended up writing my own directive which works with Universal:

import { Directive, ElementRef, Inject, Input, OnInit, PLATFORM_ID} from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

@Directive({
  selector: '[appLazyLoadImage]'
})
export class LazyLoadImageDirective implements OnInit {
  @Input() srcLazy: string;

  constructor(
    private el: ElementRef,
    @Inject(PLATFORM_ID) private platformId: Object,
  ) { }

  ngOnInit() {
    // only run lazy image loading in the browser
    if (isPlatformBrowser(this.platformId)) {
      // if browser supports IntersectionObserver
      if ('IntersectionObserver' in window) {
        const lazyImageObserver = new IntersectionObserver((entries, observer) => {
          entries.forEach((entry) => {
            if (entry.isIntersecting) {
              entry.target.setAttribute('src', this.srcLazy);
              entry.target.classList.add('lazy-loaded');
              lazyImageObserver.unobserve(entry.target);
            }
          });
        });
        lazyImageObserver.observe(this.el.nativeElement);
      } else {
        // Otherwise replace image by default
        this.el.nativeElement.setAttribute('src', this.srcLazy);
      }
    }
  }

}

import it into your Module:

import { LazyLoadImageDirective } from './lazy-load-image.directive';
...
@NgModule({
  imports: [
    CommonModule
  ],
  declarations: [
    LazyLoadImageDirective
  ],
  exports: [
    CommonModule,
    LazyLoadImageDirective
  ]
})
...etc

and use on an image with:
<img src="../assets/placeholder.jpg" srcLazy="../assets/myimage.jpg" alt="Example" appLazyLoadImage />

I also got the same error, I have angular universal installed

(function (exports, require, module, __filename, __dirname) { import { LazyLoadImageDirective } from './src/lazyload-image.directive';
                                                              ^^^^^^

SyntaxError: Unexpected token import
    at createScript (vm.js:80:10)
    at Object.runInThisContext (vm.js:139:10)
    at Module._compile (module.js:617:28)
    at Object.Module._extensions..js (module.js:664:10)
    at Module.load (module.js:566:32)
    at tryModuleLoad (module.js:506:12)
    at Function.Module._load (module.js:498:3)
    at Module.require (module.js:597:17)
    at require (internal/module.js:11:18)
    at eval (webpack:///external_%22ng-lazyload-image%22?:1:18) ```

works with this

added this on webpack config

externals: [ nodeExternals({ whitelist: [ /^ng-lazyload-image/, ] })

I guess angular universal only can import ComonJS modules and since ng-lazyload-image only have ES-modules as target angular universal fails.

I guess we have to set up multiple targets for ng-lazyload-image, as @kmturley describe. But that would also mean that tree shaking won't work.

@xmasuku, can you give a more detail example of your webpack config? Where do you use whitelist?

@tjoskar I used my custom webpack build, I was not using angular cli

I have generated a cli project and I get the same error

Okay, I will try to take a look at it tonight

I experience the same issue while trying to build with angular universal. @tjoskar have you thought about using ng-packagr to build your library? You could build and bundle your library in FESM2015, FESM5, and UMD formats.
Have a look here: https://github.com/ng-packagr/ng-packaged/blob/master/package.json#L11 (example project using ng-packagr).

// Looks like its the common issue for universal eg. https://github.com/angular/angular-cli/issues/7200
// I have this setup on my project
// replace this: --- "compile:server": "tsc -p server.tsconfig.json"
// with this: --- "compile:server": "node --max_old_space_size=3072 node_modules/webpack/bin/webpack.js --config webpack.server.config.js --progress --colors"

// then create webpack.server.config.js add code below:

const path = require('path');
const webpack = require('webpack');
const nodeExternals = require('webpack-node-externals');

module.exports = {
  mode: 'none',
  entry: {
    // This is our Express server for Dynamic universal
    server: './server.ts'
  },
  target: 'node',
  resolve: { extensions: ['.ts', '.js'] },
  externals: [ nodeExternals({
    whitelist: [
        /^ng-lazyload-image/,
    ]
  }), /.*?webpack.*?/i ],
  optimization: {
    minimize: false
  },
  output: {
    // Puts the output at the root of the dist folder
    path: path.join(__dirname, 'dist'),
    filename: '[name].js'
  },
  module: {
    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'),
      {}
    )
  ]
};

// my server.ts

```
import 'zone.js/dist/zone-node';
import {enableProdMode} from '@angular/core';
// Express Engine
import {ngExpressEngine} from '@nguniversal/express-engine';
// Import module map for lazy loading
import {provideModuleMap} from '@nguniversal/module-map-ngfactory-loader';

import * as express from 'express';
import {join} from 'path';

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

// Express server
const app = express();

const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist/browser');

// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = 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);

// Example Express Rest API endpoints
// app.get('/api/*', (req, res) => { });
// Serve static files from /browser
app.get('
.*', express.static(DIST_FOLDER, {
maxAge: '1y'
}));

// All regular routes use the Universal engine
app.get('*', (req, res) => {
res.render('index', { req });
});

// Start up the Node server
app.listen(PORT, () => {
console.log(Node Express server listening on http://localhost:${PORT});
});

For now, this should work.

@tjoskar I used your branch and it seems to fix the issue with the build error. Do you have a plan when you are going to merge and release it?

@Loutrinos, Sorry for the delay. Can you please try to install [email protected] (beta) to see if that solves the problem. Thanks.

@tjoskar I see in the 5.1.0 release that the structure is different. This is ok for now as it's a beta release.
With 5.1.0 Angular Universal doesn't break anymore :P

@Loutrinos,

I see in the 5.1.0 release that the structure is different

Sorry about that, I created a new release ([email protected]) with the same structure as before. My previous weeks have been quite hectic but now I'm back to business so let me know how it works for you.

After installed 5.1.1 version and I'm getting this error:
ReferenceError: IntersectionObserver is not defined

@agustintarifa How does your tsconfig.json file looks like?

I just created this repo: https://github.com/tjoskar/ng-lazyload-image-bugs/tree/master/370-universal-starter-compile and it seams to work fine. I cloned https://github.com/angular/universal-starter, installed ng-lazyload-image (npm install [email protected]), added an image, compiled with npm run build:prerender and then started the server: node dist/server.js.

Thanks for the answer @tjoskar
It's working now, I changed my modules.
In the app.module use: LazyLoadImageModule.forRoot({}),
And in the other modules: LazyLoadImageModule.forRoot({ preset: intersectionObserverPreset, }),

The only problem, the offset is not working at the top to see the first image I need to scroll 1px at least and then the other one loads when are visible, I want to load the images 200px before

Humm.. In my example above I only declare LazyLoadImageModule in app.module and it is working fine and I can't really reproduce the error. It would be super helpful if some one could create a small repo to reproduce the error (or provide step by step instructions).

@agustintarifa, Regarding the offset problem. Can you create a new issue for that?

Hi, when can we expect v5.1.1 as a stable not beta? Because i have this problem with SSR too.

@vytautas-pranskunas-, I will release it tomorrow with some other smaller fixes.

Siunds great 👍

On Mon, Apr 1, 2019, 9:17 PM Oskar Karlsson notifications@github.com
wrote:

@vytautas-pranskunas- https://github.com/vytautas-pranskunas-, I will
release it tomorrow with some other smaller fixes.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/tjoskar/ng-lazyload-image/issues/370#issuecomment-478709421,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ADvMl18aKahOsS6vr4C4h4Tagwy2p4TNks5vclsrgaJpZM4Z0lGg
.

[email protected] is now out. Let me know it the error still occurs.

@tjoskar I've been using 6.0.0 and the problem continues here.

// app.module.ts

LazyLoadImageModule.forRoot({
  preset: intersectionObserverPreset
}),

And when I start the SSR server I got:

ReferenceError: IntersectionObserver is not defined

ref #396

Was this page helpful?
0 / 5 - 0 ratings

Related issues

alisahinozcelik picture alisahinozcelik  ·  4Comments

coryrylan picture coryrylan  ·  7Comments

walfro picture walfro  ·  11Comments

vincent-cm picture vincent-cm  ·  10Comments

audacitus picture audacitus  ·  5Comments