angular/universal

Component 'f' is not resolved - templateUrl Did you run and wait for 'resolveComponentResources()'?

karamjeetclerisy opened this issue · 2 comments

I am trying to implement Angular Universal to an Angular 15 application that uses window and jquery. I have added the JSDOM package to use window object in server.ts and also added webpack.server.config.ts file for transpiling files like "canvas.node". The build gets generated successfully with "npm run build:ssr" and when I serve the application with "npm run serve:ssr" then It says Node Express server listening on http://localhost:4000 as expected but when I hit this url in browser then It throws the following error.


Error: Component 'f' is not resolved:
 - templateUrl: ../../templates/header/header.component.html
Did you run and wait for 'resolveComponentResources()'?
    at Function.get (path_to_application/dist/server.js:2:1798018)
    at Fe (path_to_application/dist/server.js:2:1631172)
    at Aa (path_to_application/dist/server.js:2:1678967)
    at path_to_application/dist/server.js:2:1679384
    at path_to_application/dist/server.js:2:1652886
    at Array.forEach ()
    at pi (path_to_application/dist/server.js:2:1652849)
    at path_to_application/dist/server.js:2:1652878
    at Array.forEach ()
    at pi (path_to_application/dist/server.js:2:1652849)

The header.component.html file does exists at the specified location. However, I have also tried to place the html file at the location same as header.component.ts and also changed from templateUrl: ../../templates/header/header.component.html to templateUrl: ./header.component.html but I am still getting the same error

package.json


{
  "name": "myapp",
  "productName": "myapp",
  "version": "0.0.1",
  "description": "myapp",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "webpack --mode production",
    "watch": "ng build --watch --configuration development",
    "lint": "ng lint",
    "dev:ssr": "ng run myapp:serve-ssr",
    "build:ssr": "ng build --output-hashing=none && npm run webpack:server",
    "serve:ssr": "node dist/server.js",
    "webpack:server": "webpack --mode production --config webpack.server.config.js",
    "prerender": "ng run myapp:prerender"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^15.2.0",
    "@angular/common": "^15.2.0",
    "@angular/compiler": "^15.2.0",
    "@angular/core": "^15.2.0",
    "@angular/forms": "^15.2.0",
    "@angular/platform-browser": "^15.2.0",
    "@angular/platform-browser-dynamic": "^15.2.0",
    "@angular/platform-server": "^15.2.0",
    "@angular/router": "^15.2.0",
    "@electron/remote": "^2.0.9",
    "@nguniversal/express-engine": "^15.2.1",
    "@ngx-loading-bar/core": "^6.0.2",
    "@ngx-loading-bar/http-client": "^6.0.2",
    "@ngx-loading-bar/router": "^6.0.2",
    "@ngx-translate/core": "^14.0.0",
    "@ngx-translate/http-loader": "^7.0.0",
    "@types/jsdom": "^21.1.1",
    "angular-router-loader": "^0.8.5",
    "angular2-draggable": "^2.3.2",
    "angular2-template-loader": "^0.6.2",
    "bootstrap": "^4.6.2",
    "canvas": "^2.11.2",
    "core-js": "^3.26.1",
    "domino": "^2.1.6",
    "electron-log": "^4.4.8",
    "electron-progressbar": "^2.0.1",
    "electron-updater": "^5.3.0",
    "express": "^4.15.2",
    "jquery": "^3.6.2",
    "jsdom": "^22.0.0",
    "localstorage-polyfill": "^1.0.1",
    "mock-browser": "^0.92.14",
    "ng-marquee": "^9.5.0",
    "ng4-click-outside": "^1.0.1",
    "ngx-cookie-service": "^15.0.0",
    "ngx-device-detector": "^4.0.1",
    "ngx-electron": "^2.2.0",
    "ngx-malihu-scrollbar": "^9.0.0",
    "ngx-mask": "^15.0.2",
    "ngx-order-pipe": "^2.2.0",
    "ngx-progressbar": "^9.0.0",
    "ngx-webstorage": "^11.1.1",
    "node-loader": "^2.0.0",
    "npm-check-updates": "^16.6.2",
    "popper.js": "^1.16.1",
    "regexp-replace-loader": "^1.0.1",
    "rxjs": "~7.8.0",
    "rxjs-compat": "^6.6.7",
    "script-ext-html-webpack-plugin": "^2.1.5",
    "time-ago-pipe": "^1.3.2",
    "ts-loader": "^9.4.2",
    "tslib": "^2.3.0",
    "webpack-node-externals": "^3.0.0",
    "zone.js": "~0.12.0"
  },
  "devDependencies": {
    "@angular-builders/custom-webpack": "^15.0.0",
    "@angular-devkit/build-angular": "^15.2.4",
    "@angular-devkit/build-optimizer": "^0.1302.1",
    "@angular/cli": "~15.2.4",
    "@angular/compiler-cli": "^15.2.0",
    "@babel/core": "^7.20.7",
    "@babel/preset-env": "^7.20.2",
    "@ngtools/webpack": "^15.2.4",
    "@nguniversal/builders": "^15.2.1",
    "@types/express": "^4.17.0",
    "@types/jasmine": "~4.3.0",
    "@types/jasminewd2": "^2.0.10",
    "@types/jquery": "^3.5.14",
    "@types/node": "^18.11.18",
    "autoprefixer": "^10.4.13",
    "babel-core": "^6.26.3",
    "babel-loader": "^9.1.0",
    "babel-preset-es2020": "^1.0.2",
    "babel-preset-stage-0": "^6.24.1",
    "circular-dependency-plugin": "^5.2.2",
    "clean-webpack-plugin": "^4.0.0",
    "codelyzer": "^6.0.2",
    "copy-webpack-plugin": "^11.0.0",
    "css-loader": "^6.7.3",
    "electron": "^13.6.6",
    "electron-builder": "^23.6.0",
    "file-loader": "^6.2.0",
    "grunt": "^1.5.3",
    "grunt-contrib-clean": "^2.0.1",
    "grunt-contrib-copy": "^1.0.0",
    "grunt-exec": "^3.0.0",
    "grunt-http": "^2.3.3",
    "grunt-string-replace": "^1.3.3",
    "html-loader": "^4.2.0",
    "html-webpack-plugin": "^5.5.0",
    "istanbul-instrumenter-loader": "^3.0.1",
    "jasmine-core": "~4.5.0",
    "jasmine-spec-reporter": "^7.0.0",
    "karma": "~6.4.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.0.0",
    "less-loader": "^11.1.0",
    "postcss-import": "^15.1.0",
    "postcss-loader": "^7.0.2",
    "postcss-url": "^10.1.3",
    "protractor": "^7.0.0",
    "raw-loader": "^4.0.2",
    "sass-loader": "^13.2.0",
    "string-replace-loader": "^3.1.0",
    "style-loader": "^3.3.1",
    "stylus-loader": "^7.1.0",
    "terser-webpack-plugin": "^5.3.6",
    "to-string-loader": "^1.2.0",
    "ts-node": "^10.9.1",
    "tslint": "^6.1.3",
    "typescript": "~4.9.4",
    "url-loader": "^4.1.1",
    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.1",
    "webpack-dev-server": "^4.11.1",
    "webpack-plugin-hash-output": "^3.2.1"
  },
  "optionalDependencies": {
    "bufferutil": "^4.0.7",
    "utf-8-validate": "^6.0.3"
  }
}

server.ts


import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { enableProdMode } from '@angular/core';

import { APP_BASE_HREF } from '@angular/common';
import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { existsSync, readFileSync } from 'fs';
import { join } from 'path';
import { AppServerModule } from './src/main.server';
import { JSDOM } from 'jsdom';
import 'localstorage-polyfill';

enableProdMode();

// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
  const server = express();
  const distFolder = join(process.cwd(), 'dist/browser');
  const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
  
  const template = readFileSync(join(distFolder, 'index.html')).toString();
  const constants = readFileSync(join(distFolder, 'constants' , 'constants.js')).toString();
  /*
    "constants.js" file contains the following line:
    window.MY_CONSTANTS = { APP_TITLE: 'Hello World' }
  */
  const dom = new JSDOM(template+`<script>`+constants+`</script>`, {runScripts: "dangerously", url: 'http://localhost:4000'});

const win = dom.window;

global['window'] = win;
global['MY_CONSTANTS'] = win['MY_CONSTANTS'];
global['document'] = win.document;
global['DOMTokenList'] = win.DOMTokenList;
global['Node'] = win.Node;
global['Text'] = win.Text;
global['HTMLElement'] = win.HTMLElement;
global['navigator'] = win.navigator;
global['localStorage'] = localStorage;

// Our Universal express-engine (found @ https://github.com/angular/universal/tree/main/modules/express-engine)
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));

server.set('view engine', 'html');
server.set('views', distFolder);

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

// All regular routes use the Universal engine
server.get('*', (req, res) => {
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
});

return server;
}

function run(): void {
const port = process.env['PORT'] || 4000;

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

// Webpack will replace 'require' with 'webpack_require'
// 'non_webpack_require' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const non_webpack_require: NodeRequire;
const mainModule = non_webpack_require.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
run();
}

export * from './src/main.server';

app.server.module.ts


import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';

import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
  imports: [
    AppModule,
    ServerModule,
  ],
  bootstrap: [AppComponent],
})
export class AppServerModule {}

webpack.server.config.ts


const path = require('path');
const _root = path.resolve(__dirname);
const ngw = require('@ngtools/webpack');
const webpack = require('webpack');
function root(args) {
  args = Array.prototype.slice.call(arguments, 0);
  return path.join.apply(path, [_root].concat(args));
}
module.exports = {
  entry: { server: './server.ts' },
  resolve: { extensions: ['.js', '.ts'] },
  target: 'node',
  externals: [/(node_modules|main\..*\.js)/ ],
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      },
      { test: /\.ts$/, loader: 'ts-loader' },
      {
        test: /\.(cjs|cts|mjs|mts)$/,
        use: [
            {
                loader: 'babel-loader',
                options: {
                    cacheDirectory: true,
                    compact: false,
                    plugins: ['@angular/compiler-cli/linker/babel'],
                },
            },
        ],
      },
      {
        test: /.*\.(jsx|tsx)$/,
        exclude: /node_modules/,
        loader: '@ngtools/webpack',
      },
      {
          test: /\.node$/,
          use: 'node-loader'
      }
    ]
  },
  plugins: [
    new ngw.AngularWebpackPlugin({
      tsconfig: root('tsconfig.server.json')
    }),
    new webpack.ContextReplacementPlugin(
      /(.+)?angular(\\|\/)core(.+)?/,
      path.join(__dirname, 'src'), // location of your src
      {} // a map of your routes
    ),
    new webpack.ContextReplacementPlugin(
      /(.+)?express(\\|\/)(.+)?/,
      path.join(__dirname, 'src'),
      {}
    )
  ]
};

tsconfig.server.json


/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./out-tsc/server",
    "baseUrl": "./",
    "module": "commonjs",
    "types": [
      "node"
    ]
  },
  "exclude": [
    "src/test.ts",
    "**/**/*.spec.ts"
  ],
  "files": [
    "src/main.server.ts",
    "server.ts"
  ]
}

###tsconfig.json


{
  "compileOnSave": false,
  "compilerOptions": {
    "downlevelIteration": true,
    "importHelpers": true,
    "module": "commonjs",
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "strictPropertyInitialization": false,
    "noImplicitAny": false,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": false,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es6",
    "useDefineForClassFields": false,
    "typeRoots": [
      "node_modules/@types"
    ],
    "types": [
      "jquery",
      "mcustomscrollbar"
    ],
    "lib": [
      "ES2022",
      "dom"
    ]
  },
  "exclude": [
    "node_modules",
    "**/*.spec.ts",
    "e2e/*.e2e-spec.ts",
    "e2e/app.po.ts",
    "**/test.ts"
  ],
  "angularCompilerOptions": {
    "enableI18nLegacyMessageIdFormat": false,
    "strictInjectionParameters": false,
    "strictInputAccessModifiers": false,
    "strictTemplates": false
  }
}

Note: I am not using BrowserTransferStateModule and ServerTransferStateModule as they are deprecated in Angular 15.

Custom webpack configuration are not supported by the Angular team. If the problem persist when using the Angular CLI please file a new issue with a minimal reproduction.

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.