javascriptangulartypescriptserver-side-renderingmjs

Angular 17 SSR - how to compile server files (*.mjs) to *.js


my hosting provider requires server files to be compiled to CommonJs and Angular 17 by default compiles files to Module JS *.mjs, I've tried to change the tsconfig.json but it changes the scope for the whole app but I want to change it just for server files. I've been looking for some documentation how to use something like tsconfig.server.json but I don't know how to later split it in angular.json file.

How to convert *.mjs files to *.js for SSR in Angular 17?

enter image description here

default tsconfig.json file:

/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
  "compileOnSave": false,
  "compilerOptions": {
    "outDir": "./dist/out-tsc",
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "esModuleInterop": true,
    "sourceMap": true,
    "declaration": false,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "ES2022",
    "module": "ES2022",
    "useDefineForClassFields": false,
    "lib": ["ES2022", "dom"]
  },
  "angularCompilerOptions": {
    "enableI18nLegacyMessageIdFormat": false,
    "strictInjectionParameters": true,
    "strictInputAccessModifiers": true,
    "strictTemplates": true
  }
}


Solution

  • Base on this idea, I found the answer, Thanks yannier

    1: Modify server.ts

    import 'zone.js/node';
    
    import { APP_BASE_HREF } from '@angular/common';
    import { CommonEngine } from '@angular/ssr';
    import express from 'express';
    import { fileURLToPath } from 'url';
    import { dirname, join, resolve } from 'path';
    import bootstrap from './src/main.server';
    
    // The Express app is exported so that it can be used by serverless Functions.
    export function app(): express.Express {
      const server = express();
      const serverDistFolder = dirname(fileURLToPath(import.meta.url));
      const browserDistFolder = resolve(serverDistFolder, '../browser');
      const indexHtml = join(serverDistFolder, 'index.server.html');
    
      const commonEngine = new CommonEngine();
    
      server.set('view engine', 'html');
      server.set('views', browserDistFolder);
    
      // Example Express Rest API endpoints
      // server.get('/api/**', (req, res) => { });
      // Serve static files from /browser
      server.get(
        '*.*',
        express.static(browserDistFolder, {
          maxAge: '1y',
        })
      );
    
      // All regular routes use the Angular engine
      server.get('*', (req, res, next) => {
        const { protocol, originalUrl, baseUrl, headers } = req;
    
        commonEngine
          .render({
            bootstrap,
            documentFilePath: indexHtml,
            url: `${protocol}://${headers.host}${originalUrl}`,
            publicPath: browserDistFolder,
            providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
          })
          .then((html) => res.send(html))
          .catch((err) => next(err));
      });
    
      return server;
    }
    
    export * from './src/main.server';
    

    2: Create a js file named main.js

    async function run() {
      try {
        // Import the app from the ES module
        const server = await import("./server/server.mjs");
        const app = await server.app();
    
        const port = process.env["PORT"] || 4000;
    
        // Start up the Node server
        app.listen(port, () => {
          console.log(`Node Express server listening on http://localhost:${port}`);
        });
      } catch (error) {
        console.error("Failed to import app:", error);
      }
    }
    
    run();
    

    3: Run main.js using node main.js or if you use iis you can run it by iisnode module:

    <configuration>
      <system.web>
        <httpRuntime enableVersionHeader="true" />
      </system.web>
      <system.webServer>
        <httpProtocol>
          <customHeaders>
            <add name="Strict-Transport-Security" value="max-age=31536000"/>
            <add name="X-Content-Type-Options" value="nosniff" />
            <add name="X-Frame-Options" value="DENY" />
            <add name="X-XSS-Protection" value="1; mode=block" />
            <remove name="X-Powered-By" />
          </customHeaders>
        </httpProtocol>
        <webSocket enabled="false" />
        <handlers>
          <!-- Indicates that the main.js file is a node.js site to be handled by the iisnode module -->
          <add name="iisnode" path="main.js" verb="*" modules="iisnode"/>
        </handlers>
        <rewrite>
          <rules>
            <!-- <rule name="HTTP to HTTPS redirect" stopProcessing="true">
              <match url="(.*)" />
              <conditions>
                <add input="{HTTPS}" pattern="off" ignoreCase="true" />
              </conditions>
              <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" />
            </rule> -->
            <!-- Do not interfere with requests for node-inspector debugging -->
            <rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
              <match url="^main.js\/debug[\/]?" />
            </rule>
            <!-- All other URLs are mapped to the node.js site entry point -->
            <rule name="DynamicContent">
              <match url="^(?!.*login).*$"></match>
              <conditions>
                <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true"/>
              </conditions>
              <action type="Rewrite" url="main.js"/>
            </rule>
          </rules>
          <!-- <outboundRules>
            <rule name="Add Strict-Transport-Security when HTTPS" enabled="true">
              <match serverVariable="RESPONSE_Strict_Transport_Security" pattern=".*" />
              <conditions>
                <add input="{HTTPS}" pattern="on" ignoreCase="true" />
              </conditions>
              <action type="Rewrite" value="max-age=31536000" />
            </rule>
          </outboundRules> -->
        </rewrite>
        <!-- 'bin' directory has no special meaning in node.js and apps can be placed in it -->
        <security>
          <requestFiltering>
            <hiddenSegments>
              <remove segment="bin"/>
            </hiddenSegments>
          </requestFiltering>
        </security>
        <!-- Make sure error responses are left untouched -->
        <httpErrors existingResponse="PassThrough" />
    
        <!-- Restart the server if any of these files change -->
        <iisnode watchedFiles="web.config;*.js;browser/*.*" nodeProcessCommandLine="C:\Program Files\nodejs\node.exe" />
      </system.webServer>
    </configuration>
    
      - browser (folder)
      - server (folder)
      - main.js
      - web.config (in iis)