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:
DashboardMerchantPortalGui
GuiTable
MerchantProfileMerchantPortalGui
ProductMerchantPortalGui
ProductOfferMerchantPortalGui
SalesMerchantPortalGui
SecurityMerchantPortalGui
UserMerchantPortalGui
ZedUi
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", } }
2. Update or add the following dependencies:
```json
{
"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"
}
}
```
3. Update or add the following dev dependencies:
```json
{
"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", } }
5. Update and install the package dependencies:
```bash
rm -rf node_modules
npm install
```
<section class='info-block info-block--warning'><i class='info-block__icon icon-warning'></i><div class='info-block__content'><div class="info-block__title">Verification</div>
Ensure that the `package-lock.json` file and the `node_modules` folder have been updated.
</div></section>
3) Update the Angular configuration
-
In the
frontend/merchant-portal
folder, do the following:-
In
jest.config.ts
, change the resolver fromnrwl
tonx
:export default { ..., resolver: '@nx/jest/plugins/resolver', ... }
-
In
jest.preset.js
, changenxPreset
fromnrwl
tonx
:const nxPreset = require('@nx/jest/preset').default;
-
In
test-setup.ts
, replacecore-js/features/reflect
import toreflect-metadata/lite
:import 'reflect-metadata/lite'; import 'jest-preset-angular/setup-jest';
-
In
tsconfig.spec.json
, changecompileOptions
to 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
, updategetMPEntryPointsMap
function 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 thepublicPath
config value:
export default async (...): Promise<webpack.Configuration> => { ...., config.output.publicPath = '/assets/js/'; .... }
-
-
In the root of the project, do the following:
- Add
.nx/cache
to.gitignore
and.prettierignore
.
.nx/cache
- Delete
angular.json
. - Add the
target
option totsconfig.mp.json
:
{ ...., "compilerOptions": { "target": "ES2022", .... } }
- In
.eslintrc.mp.json
, movets
configuration 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.json
with 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