Upgrade to Angular 17
Edit on GitHubThis document describes how to upgrade Angular to version 17 in your Spryker project.
Spryker’s current version of Angular is v15. However, Angular v15 should be deprecated on 2024-05-18, according to the Angular Support policy and schedule. The current stable version of Angular is v17.
We recommend upgrading to the latest major version of Angular to get the most recent bug fixes and security updates. Additionally, the upgrade optimizes both runtime performance and tooling.
However, updating to Angular v17 results in incompatibility with older versions of Angular. Therefore, a major release is necessary for the following modules:
DashboardMerchantPortalGuiGuiTableMerchantProfileMerchantPortalGuiProductMerchantPortalGuiProductOfferMerchantPortalGuiSalesMerchantPortalGuiSecurityMerchantPortalGuiUserMerchantPortalGuiZedUi
Estimated migration time: 2h
To upgrade to Angular v17, follow these steps.
Prerequisites
Before starting the migration, update Stylelint and css-loader as described in the Stylelint migration guide.
1) Update modules
- Check if the following Marketplace modules in your project have the new versions:
| NAME | VERSION |
|---|---|
| DashboardMerchantPortalGui | >= 3.0.0 |
| GuiTable | >= 3.0.0 |
| MerchantProfileMerchantPortalGui | >= 3.0.0 |
| ProductMerchantPortalGui | >= 4.0.0 |
| ProductOfferMerchantPortalGui | >= 3.0.0 |
| SalesMerchantPortalGui | >= 3.0.0 |
| SecurityMerchantPortalGui | >= 3.0.0 |
| UserMerchantPortalGui | >= 3.0.0 |
| ZedUi | >= 3.0.0 |
If they don’t, update the module versions manually or by using the following command:
composer update spryker/dashboard-merchant-portal-gui spryker/gui-table spryker/merchant-profile-merchant-portal-gui spryker/product-merchant-portal-gui spryker/product-offer-merchant-portal-gui spryker/sales-merchant-portal-gui spryker/security-merchant-portal-gui spryker/user-merchant-portal-gui spryker/zed-ui
- Regenerate the data transfer object:
console transfer:generate
2) Update npm dependencies
In package.json, do the following:
-
Adjust the npm scripts:
{ "scripts": { "mp:build": "nx build", "mp:build:watch": "nx build --watch", "mp:build:production": "nx build --configuration production", "mp:stylelint": "node ./frontend/merchant-portal/stylelint.mjs", "mp:lint": "nx lint --no-eslintrc", "mp:test": "nx test", } } -
Update or add the following dependencies:
{
"dependencies": {
"@angular/animations": "~17.3.0",
"@angular/cdk": "~17.3.0",
"@angular/common": "~17.3.0",
"@angular/compiler": "~17.3.0",
"@angular/core": "~17.3.0",
"@angular/elements": "~17.3.0",
"@angular/forms": "~17.3.0",
"@angular/platform-browser": "~17.3.0",
"@angular/platform-browser-dynamic": "~17.3.0",
"@angular/router": "~17.3.0",
"rxjs": "~7.8.1",
"zone.js": "~0.14.4"
}
}
-
Update or add the following dev dependencies:
{ "devDependencies": { "@angular-devkit/build-angular": "~17.3.2", "@angular-builders/custom-webpack": "~17.0.1", "@angular-eslint/eslint-plugin": "~17.3.0", "@angular-eslint/eslint-plugin-template": "~17.3.0", "@angular-eslint/template-parser": "17.3.0", "@angular/cli": "~17.3.0", "@angular/compiler-cli": "~17.3.0", "@angular/language-service": "~17.3.0", "@nx/angular": "~18.1.1", "@nx/eslint": "~18.1.2", "@nx/eslint-plugin": "~18.1.2", "@nx/jest": "~18.1.2", "@types/jest": "~29.5.12", "@types/node": "~18.16.9", "@typescript-eslint/eslint-plugin": "~7.3.1", "@typescript-eslint/parser": "~7.3.1", "babel-loader": "~9.1.3", "eslint": "~8.57.0", "eslint-plugin-deprecation": "~2.0.0", "fast-glob": "~3.3.2", "jest": "~29.7.0", "jest-environment-jsdom": "~29.7.0", "jest-preset-angular": "~13.1.6", "nx": "~18.1.2", "reflect-metadata": "~0.2.1", "tslib": "~2.6.2", "typescript": "~5.4.2", } } -
Remove the following dependencies:
{ "dependencies": { "core-js": "~3.29.1", }, "devDependencies": { "@angular-devkit/build-angular": "~15.2.9", "@angular-eslint/builder": "~15.0.0", "@angular-eslint/schematics": "~15.0.0", "@nrwl/cli": "~15.0.7", "@nrwl/jest": "~15.0.7", "@nrwl/workspace": "~15.0.7", } } -
Update and install the package dependencies:
rm -rf node_modules
npm install
Ensure that the package-lock.json file and the node_modules folder have been updated.
3) Update the Angular configuration
-
In the
frontend/merchant-portalfolder, do the following:-
In
jest.config.ts, change the resolver fromnrwltonx:export default { ..., resolver: '@nx/jest/plugins/resolver', ... } -
In
jest.preset.js, changenxPresetfromnrwltonx:const nxPreset = require('@nx/jest/preset').default; -
In
test-setup.ts, replacecore-js/features/reflectimport toreflect-metadata/lite:import 'reflect-metadata/lite'; import 'jest-preset-angular/setup-jest'; -
In
tsconfig.spec.json, changecompileOptionsto the following:{ "compilerOptions": { ... "esModuleInterop": true, "target": "ES2015", "types": ["jest", "node"] }, } -
In
utils.js, delete import from'@angular-devkit/core'and replace it with custom function:
const glob = require('fast-glob'); function dasherize(str) { return str .replace(/[\s_]/g, '-') .replace(/([a-z])([A-Z])/g, '$1-$2') .toLowerCase(); } async function getMPEntryPoints(directory, entryPath) { return glob(entryPath, { cwd: directory, }); } function entryPointPathToName(prefix, path) { return prefix + dasherize(path.split('/')[0]); } module.exports = { getMPEntryPoints, entryPointPathToName, };- In
entry-points.js, updategetMPEntryPointsMapfunction to add the possibility of one entry build:
const { readFileSync } = require('fs'); const MP_SINGLE_ENTRY = 'spy/merchant-portal'; const MP_SINGLE_ENTRY_MARKER = `${MP_SINGLE_ENTRY}:single-entry-marker`; async function getMPEntryPointsMap() { const singleEntryNames = new Map(); const entryPointsMap = async (dir, entryPath) => { const entryPoints = await getMPEntryPoints(dir, entryPath); return entryPoints.reduce((acc, entryPoint) => { const fullPath = path.join(dir, entryPoint); const isSingleEntry = readFileSync(fullPath, { encoding: 'utf8' }).includes(MP_SINGLE_ENTRY_MARKER); const name = entryPointPathToName('spy/', entryPoint); if (isSingleEntry || singleEntryNames.has(name)) { singleEntryNames.set(name, fullPath); return acc; } return { ...acc, [name]: fullPath }; }, {}); }; const core = await entryPointsMap(ROOT_SPRYKER_CORE_DIR, MP_CORE_ENTRY_POINT_FILE); const project = await entryPointsMap(ROOT_SPRYKER_PROJECT_DIR, MP_PROJECT_ENTRY_POINT_FILE); return { ...core, ...project, [MP_SINGLE_ENTRY]: [...singleEntryNames.values()] }; }- In
webpack.config.ts, add thepublicPathconfig value:
export default async (...): Promise<webpack.Configuration> => { ...., config.output.publicPath = '/assets/js/'; .... } -
-
In the root of the project, do the following:
- Add
.nx/cacheto.gitignoreand.prettierignore.
.nx/cache- Delete
angular.json. - Add the
targetoption totsconfig.mp.json:
{ ...., "compilerOptions": { "target": "ES2022", .... } }- In
.eslintrc.mp.json, movetsconfiguration to the overrides section. Replace the plugin and disable some rules:
{ "root": true, "plugins": ["@nx"], "overrides": [ { "files": ["*.ts"], ...., "parser": "@typescript-eslint/parser", "extends": [ "./node_modules/@spryker/frontend-config.eslint/.eslintrc.js", "plugin:@nx/typescript", "plugin:@nx/angular", "plugin:@angular-eslint/template/process-inline-templates" ], "parserOptions": { "project": "./tsconfig.mp.json" }, "rules": { ..., "max-lines": "off", } }, { "files": ["*.html"], ...., "rules": { .... "@typescript-eslint/ban-types": 0, "@typescript-eslint/no-empty-interface": 0, "@typescript-eslint/no-explicit-any": 0, "@typescript-eslint/no-unused-vars": 0, "@angular-eslint/no-host-metadata-property": 0, "@angular-eslint/directive-class-suffix": 0, "no-prototype-builtins": 0 } } ] }- Add
project.jsonwith the following configuration:
{ "name": "merchant-portal", "$schema": "node_modules/nx/schemas/project-schema.json", "sourceRoot": ".", "projectType": "application", "prefix": "mp", "generators": {}, "targets": { "build": { "executor": "@angular-builders/custom-webpack:browser", "options": { "customWebpackConfig": { "path": "./frontend/merchant-portal/webpack.config.ts", "mergeRules": {} }, "indexTransform": "./frontend/merchant-portal/html-transform.js", "outputPath": "public/MerchantPortal/assets/js", "baseHref": "/assets/js/", "index": "src/Pyz/Zed/ZedUi/Presentation/Components/index.html", "main": "src/Pyz/Zed/ZedUi/Presentation/Components/main.ts", "polyfills": "src/Pyz/Zed/ZedUi/Presentation/Components/polyfills.ts", "tsConfig": "tsconfig.mp.json", "assets": [ { "glob": "*/src/Spryker/Zed/*/Presentation/Components/assets/**/*", "input": "vendor/spryker/spryker/Bundles", "output": "/assets/" }, { "glob": "*/Presentation/Components/assets/**/*", "input": "src/Pyz/Zed", "output": "/assets/" } ], "styles": [ "vendor/spryker/zed-ui/src/Spryker/Zed/ZedUi/Presentation/Components/styles.less", "src/Pyz/Zed/ZedUi/Presentation/Components/styles.less" ], "scripts": [] }, "configurations": { "development": { "buildOptimizer": false, "optimization": false, "vendorChunk": true, "extractLicenses": false, "sourceMap": true, "namedChunks": true }, "production": { "fileReplacements": [ { "replace": "src/Pyz/Zed/ZedUi/Presentation/Components/environments/environment.ts", "with": "src/Pyz/Zed/ZedUi/Presentation/Components/environments/environment.prod.ts" } ], "optimization": true, "outputHashing": "none", "sourceMap": false, "namedChunks": false, "extractLicenses": true, "vendorChunk": true, "buildOptimizer": true, "budgets": [ { "type": "bundle", "maximumWarning": "2mb", "maximumError": "5mb" } ] } }, "defaultConfiguration": "development" }, "lint": { "executor": "@nx/eslint:lint", "options": { "eslintConfig": ".eslintrc.mp.json", "lintFilePatterns": [ "src/Pyz/Zed/*/Presentation/Components/**/*.ts", "src/Pyz/Zed/*/Presentation/Components/**/*.html" ], "noEslintrc": true } }, "test": { "executor": "@nx/jest:jest", "options": { "jestConfig": "frontend/merchant-portal/jest.config.ts" } } } } - Add
4) Manual Spryker module updating
If you can’t update the modules automatically with the composer update command, update all angular dependencies in the root of the module inside package.json to ^17.3.0 version and all @spryker/* dependencies to the next major.0.0 version, for example, 1.1.0 => ^2.0.0,0.1.4 => ^1.0.0).
To add a single entry point support, add // spy/merchant-portal:single-entry-marker line in the entry.ts file, for example, like this:
// spy/merchant-portal:single-entry-marker
import { registerNgModule } from '@mp/zed-ui';
import { ComponentsModule } from './app/components.module';
registerNgModule(ComponentsModule);
Delete additional JS injecting in the module layout_file_name twig file. For example, delete the whole block below:
{% block footerJs %}
{{ view.importJsBundle('agent-dashboard-merchant-portal-gui', importConfig) }}
{{ parent() }}
{% endblock %}
Thank you!
For submitting the form