From e9da3db560fcd62586771a52d5be7f3996ddcf18 Mon Sep 17 00:00:00 2001 From: Isaac Mann Date: Thu, 29 Feb 2024 15:10:49 -0500 Subject: [PATCH] docs(core): remove angularjs migration (#21916) --- docs/generated/manifests/menus.json | 32 - docs/generated/manifests/nx-api.json | 6 +- docs/generated/manifests/nx.json | 44 - docs/generated/packages-metadata.json | 6 +- .../packages/angular/documents/overview.md | 2 - docs/map.json | 5 - docs/shared/mental-model/large-tasks.json | 8 - ...migration-angularjs-unit-tests-passing.png | Bin 38302 -> 0 bytes docs/shared/migration/migration-angularjs.md | 818 ------------------ .../shared/packages/angular/angular-plugin.md | 2 - docs/shared/reference/sitemap.md | 1 - nx-dev/nx-dev/redirect-rules.js | 4 +- packages-legacy/angular/package.json | 2 +- packages/angular/package.json | 2 +- 14 files changed, 11 insertions(+), 921 deletions(-) delete mode 100644 docs/shared/migration/migration-angularjs-unit-tests-passing.png delete mode 100644 docs/shared/migration/migration-angularjs.md diff --git a/docs/generated/manifests/menus.json b/docs/generated/manifests/menus.json index c1e5a6735e..d8fef1c879 100644 --- a/docs/generated/manifests/menus.json +++ b/docs/generated/manifests/menus.json @@ -1578,14 +1578,6 @@ "isExternal": false, "children": [], "disableCollapsible": false - }, - { - "name": "Migrating from AngularJS", - "path": "/recipes/angular/migration/angularjs", - "id": "angularjs", - "isExternal": false, - "children": [], - "disableCollapsible": false } ], "disableCollapsible": false @@ -2769,14 +2761,6 @@ "isExternal": false, "children": [], "disableCollapsible": false - }, - { - "name": "Migrating from AngularJS", - "path": "/recipes/angular/migration/angularjs", - "id": "angularjs", - "isExternal": false, - "children": [], - "disableCollapsible": false } ], "disableCollapsible": false @@ -2853,14 +2837,6 @@ "isExternal": false, "children": [], "disableCollapsible": false - }, - { - "name": "Migrating from AngularJS", - "path": "/recipes/angular/migration/angularjs", - "id": "angularjs", - "isExternal": false, - "children": [], - "disableCollapsible": false } ], "disableCollapsible": false @@ -2889,14 +2865,6 @@ "children": [], "disableCollapsible": false }, - { - "name": "Migrating from AngularJS", - "path": "/recipes/angular/migration/angularjs", - "id": "angularjs", - "isExternal": false, - "children": [], - "disableCollapsible": false - }, { "name": "Use Environment Variables in Angular", "path": "/recipes/angular/use-environment-variables-in-angular", diff --git a/docs/generated/manifests/nx-api.json b/docs/generated/manifests/nx-api.json index 6df0aad1be..da21e9ef07 100644 --- a/docs/generated/manifests/nx-api.json +++ b/docs/generated/manifests/nx-api.json @@ -3,12 +3,12 @@ "githubRoot": "https://github.com/nrwl/nx/blob/master", "name": "angular", "packageName": "@nx/angular", - "description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.", + "description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.", "documents": { "/nx-api/angular/documents/overview": { "id": "overview", "name": "Overview", - "description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.", + "description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.", "file": "generated/packages/angular/documents/overview", "itemList": [], "isExternal": false, @@ -19,7 +19,7 @@ "/nx-api/angular/documents/angular-nx-version-matrix": { "id": "angular-nx-version-matrix", "name": "Angular and Nx Version Matrix", - "description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.", + "description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.", "file": "generated/packages/angular/documents/angular-nx-version-matrix", "itemList": [], "isExternal": false, diff --git a/docs/generated/manifests/nx.json b/docs/generated/manifests/nx.json index ad6c972c62..60cd760260 100644 --- a/docs/generated/manifests/nx.json +++ b/docs/generated/manifests/nx.json @@ -2157,17 +2157,6 @@ "isExternal": false, "path": "/recipes/angular/migration/angular-manual", "tags": [] - }, - { - "id": "angularjs", - "name": "Migrating from AngularJS", - "description": "", - "mediaImage": "", - "file": "shared/migration/migration-angularjs", - "itemList": [], - "isExternal": false, - "path": "/recipes/angular/migration/angularjs", - "tags": [] } ], "isExternal": false, @@ -3788,17 +3777,6 @@ "isExternal": false, "path": "/recipes/angular/migration/angular-manual", "tags": [] - }, - { - "id": "angularjs", - "name": "Migrating from AngularJS", - "description": "", - "mediaImage": "", - "file": "shared/migration/migration-angularjs", - "itemList": [], - "isExternal": false, - "path": "/recipes/angular/migration/angularjs", - "tags": [] } ], "isExternal": false, @@ -3904,17 +3882,6 @@ "isExternal": false, "path": "/recipes/angular/migration/angular-manual", "tags": [] - }, - { - "id": "angularjs", - "name": "Migrating from AngularJS", - "description": "", - "mediaImage": "", - "file": "shared/migration/migration-angularjs", - "itemList": [], - "isExternal": false, - "path": "/recipes/angular/migration/angularjs", - "tags": [] } ], "isExternal": false, @@ -3954,17 +3921,6 @@ "path": "/recipes/angular/migration/angular-manual", "tags": [] }, - "/recipes/angular/migration/angularjs": { - "id": "angularjs", - "name": "Migrating from AngularJS", - "description": "", - "mediaImage": "", - "file": "shared/migration/migration-angularjs", - "itemList": [], - "isExternal": false, - "path": "/recipes/angular/migration/angularjs", - "tags": [] - }, "/recipes/angular/use-environment-variables-in-angular": { "id": "use-environment-variables-in-angular", "name": "Use Environment Variables in Angular", diff --git a/docs/generated/packages-metadata.json b/docs/generated/packages-metadata.json index b450d2575a..c07eee63e9 100644 --- a/docs/generated/packages-metadata.json +++ b/docs/generated/packages-metadata.json @@ -1,11 +1,11 @@ [ { - "description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.", + "description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.", "documents": [ { "id": "overview", "name": "Overview", - "description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.", + "description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.", "file": "generated/packages/angular/documents/overview", "itemList": [], "isExternal": false, @@ -16,7 +16,7 @@ { "id": "angular-nx-version-matrix", "name": "Angular and Nx Version Matrix", - "description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.", + "description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.", "file": "generated/packages/angular/documents/angular-nx-version-matrix", "itemList": [], "isExternal": false, diff --git a/docs/generated/packages/angular/documents/overview.md b/docs/generated/packages/angular/documents/overview.md index 32929376da..5ddbaf8e0e 100644 --- a/docs/generated/packages/angular/documents/overview.md +++ b/docs/generated/packages/angular/documents/overview.md @@ -10,7 +10,6 @@ within an Nx workspace. It provides: - Generators to help scaffold code quickly, including: - Micro Frontends - Libraries, both internal to your codebase and publishable to npm - - Upgrading AngularJS applications - Single Component Application Modules (SCAMs) - NgRx helpers. - Utilities for automatic workspace refactoring. @@ -123,5 +122,4 @@ nx g @nx/angular:service my-service - [Angular Monorepo Tutorial](/getting-started/tutorials/angular-monorepo-tutorial) - [Migrating from the Angular CLI](/recipes/angular/migration/angular) - [Setup Module Federation with Angular and Nx](/concepts/module-federation/faster-builds-with-module-federation) -- [Upgrading an AngularJS application to Angular](/recipes/angular/migration/angularjs) - [Using Tailwind CSS with Angular projects](/recipes/angular/using-tailwind-css-with-angular-projects) diff --git a/docs/map.json b/docs/map.json index 238e162053..d338db19a9 100644 --- a/docs/map.json +++ b/docs/map.json @@ -583,11 +583,6 @@ "name": "Migrating Angular Application manually", "id": "angular-manual", "file": "shared/migration/angular-manual" - }, - { - "name": "Migrating from AngularJS", - "id": "angularjs", - "file": "shared/migration/migration-angularjs" } ] }, diff --git a/docs/shared/mental-model/large-tasks.json b/docs/shared/mental-model/large-tasks.json index 8e0e215e57..caaed2be5d 100644 --- a/docs/shared/mental-model/large-tasks.json +++ b/docs/shared/mental-model/large-tasks.json @@ -26179,14 +26179,6 @@ "file": "docs/shared/migration/migration-angular.md", "hash": "15ebd87a435dad1352e54e14821eab2b4d263b45" }, - { - "file": "docs/shared/migration/migration-angularjs-unit-tests-passing.png", - "hash": "53414ab08cb6659ac6e5506f9c351ad343075064" - }, - { - "file": "docs/shared/migration/migration-angularjs.md", - "hash": "cc6399f1ab8ee3f3b50927a043d0b612b41b9266" - }, { "file": "docs/shared/migration/migration-cra.md", "hash": "3f7ddcd0014a9aec9e0b94f4884d48f314081a52" diff --git a/docs/shared/migration/migration-angularjs-unit-tests-passing.png b/docs/shared/migration/migration-angularjs-unit-tests-passing.png deleted file mode 100644 index 53414ab08cb6659ac6e5506f9c351ad343075064..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38302 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 diff --git a/docs/shared/migration/migration-angularjs.md b/docs/shared/migration/migration-angularjs.md deleted file mode 100644 index d8c98f2566..0000000000 --- a/docs/shared/migration/migration-angularjs.md +++ /dev/null @@ -1,818 +0,0 @@ -# Migrating an AngularJS Project into an Nx Workspace - -Nx offers first-class support for Angular and React out-of-the-box. But one of the questions the Nrwl team often hears from our community is how to use AngularJS (Angular 1.x) in Nx. Nx is a great choice for managing an AngularJS to Angular upgrade, or just for consolidating your existing polyrepo approach to AngularJS into a monorepo to make maintenance a little easier. - -In this article, you’ll learn how to: - -- Create an Nx workspace for an AngularJS application -- Migrate an AngularJS application into your Nx workspace -- Convert an existing build process for use in Nx -- Use Webpack to build an AngularJS application -- Run unit and end-to-end tests - -For this example, you’ll be migrating the [Real World AngularJS](https://github.com/gothinkster/angularjs-realworld-example-app) application from [Thinkster.io](https://thinkster.io/). You should clone this repo so you have access to the code before beginning. - -There is also a [repo](https://github.com/nrwl/nx-migrate-angularjs-example) that shows a completed example of this guide. - -{% github-repository url="https://github.com/nrwl/nx-migrate-angularjs-example" /%} - -{% callout type="note" title="RealWorld app vs reality" %} -The RealWorld app is a great example of an AngularJS app, but it probably doesn’t have the complexity of your own codebase. As you go along, I’ll include some recommendations on how you might apply this example to your larger, more complex application. -{% /callout %} - -## Creating your workspace - -To start migrating the Real World app, create an Nx workspace: - -```shell -npx create-nx-workspace@latest nx-migrate-angularjs -``` - -When prompted choose the `apps` preset. The other presets use certain recommended defaults for the workspace configuration. Because you have existing code with specific requirements for configuration, starting with a blank workspace avoids resetting these defaults. This will give you the ability to customize the workspace for the incoming code. - -At the next prompt, you can choose whether to use [Nx Cloud](https://nx.app) or not. By using Nx Cloud, you’ll be able to share the computation cache of operations like build, test or even your own commands with everyone working on the same project. Whether you choose to use it or not, the outcome of the migration won’t be affected and you can always change your choice later. - -```shell -? What to create in the new workspace empty [an empty workspace with a layout that works best for building apps] -? Set up remote caching using Nx Cloud (It's free and doesn't require registration.) Yes [Faster builds, run details, GitHub integration. Learn more at https://nx.app] -``` - -## Creating your app - -Your new workspace won’t have much in it because of the `apps` preset. You’ll need to generate an application to have some structure created. Add the Angular plugin to your workspace: - -```shell {% skipRescope=true %} -nx add @nx/angular -``` - -For this example, we will use Karma and Protractor, the most common unit test runner and e2e test runner for AngularJS. - -{% callout type="note" title="Unit & E2E tests" %} -Codebases with existing unit and e2e tests should continue to use whatever runner they need. We’ve chosen Karma and Protractor here because it’s the most common. If you’re going to be adding unit testing or e2e as part of this transition and are starting fresh, we recommend starting with Jest and Cypress (the default if no arguments are passed to the above command). -{% /callout %} - -With the Angular capability added, generate your application: - -{% callout type="note" title="Directory Flag Behavior Changes" %} -The command below uses the `as-provided` directory flag behavior, which is the default in Nx 16.8.0. If you're on an earlier version of Nx or using the `derived` option, omit the `--directory` flag. See the [as-provided vs. derived documentation](/deprecated/as-provided-vs-derived) for more details. -{% /callout %} - -```shell -nx generate @nx/angular:application --name=realworld --directory=apps/realword --unitTestRunner=karma --e2eTestRunner=protractor -``` - -Accept the default options for each prompt: - -```shell -? Which stylesheet format would you like to use? CSS -? Would you like to configure routing for this application? No -``` - -{% callout type="note" title="About styles" %} -The RealWorld app doesn’t have any styles to actually bundle here. They’re all downloaded from a CDN that all the RealWorld apps use. If your codebase uses something other than CSS, like Sass, you can choose that here. -{% /callout %} - -## Migrating dependencies - -Copy the dependencies from the RealWorld app’s `package.json` to the `package.json` in your workspace. Split the existing dependencies into `dependencies` (application libraries) and `devDependencies` (build and test libraries). Everything related to gulp can go into `devDependencies`. - -Your `package.json` should now look like this: - -```json {% fileName="package.json" %} -{ - "name": "nx-migrate-angularjs", - "version": "0.0.0", - "license": "MIT", - "scripts": { - "postinstall": "ngcc --properties es2015 browser module main", - "start": "nx serve", - "build": "nx build", - "test": "nx test", - "e2e": "nx e2e" - }, - "private": true, - "dependencies": { - "@angular/animations": "~13.1.0", - "@angular/common": "~13.1.0", - "@angular/compiler": "~13.1.0", - "@angular/core": "~13.1.0", - "@angular/forms": "~13.1.0", - "@angular/platform-browser": "~13.1.0", - "@angular/platform-browser-dynamic": "~13.1.0", - "@angular/router": "~13.1.0", - "angular": "^1.5.0-rc.2", - "angular-ui-router": "^0.4.2", - "marked": "^0.3.5", - "rxjs": "~7.4.0", - "tslib": "^2.0.0", - "zone.js": "~0.11.4" - }, - "devDependencies": { - "@angular-devkit/build-angular": "~13.1.0", - "@angular-eslint/eslint-plugin": "~13.0.1", - "@angular-eslint/eslint-plugin-template": "~13.0.1", - "@angular-eslint/template-parser": "~13.0.1", - "@angular/cli": "~13.1.0", - "@angular/compiler-cli": "~13.1.0", - "@angular/language-service": "~13.1.0", - "@nrwl/angular": "^13.4.6", - "@nrwl/cli": "13.4.6", - "@nrwl/eslint-plugin-nx": "13.4.6", - "@nrwl/linter": "13.4.6", - "@nrwl/workspace": "13.4.6", - "@types/jasmine": "~3.5.0", - "@types/jasminewd2": "~2.0.3", - "@types/node": "14.14.33", - "@typescript-eslint/eslint-plugin": "~5.3.0", - "@typescript-eslint/parser": "~5.3.0", - "eslint": "8.2.0", - "eslint-config-prettier": "8.1.0", - "gulp": "^3.9.1", - "gulp-angular-templatecache": "^1.8.0", - "gulp-notify": "^2.2.0", - "gulp-rename": "^1.2.2", - "gulp-uglify": "^1.5.3", - "gulp-util": "^3.0.7", - "jasmine-core": "~3.6.0", - "jasmine-spec-reporter": "~5.0.0", - "karma": "~5.0.0", - "karma-chrome-launcher": "~3.1.0", - "karma-coverage-istanbul-reporter": "~3.0.2", - "karma-jasmine": "~4.0.0", - "karma-jasmine-html-reporter": "^1.5.0", - "karma-webpack": "^5.0.0", - "marked": "^0.3.5", - "merge-stream": "^1.0.0", - "prettier": "^2.3.1", - "protractor": "~7.0.0", - "ts-node": "~9.1.1", - "typescript": "~4.4.3", - "vinyl-source-stream": "^1.1.0" - } -} -``` - -Run `npm install` to install all of your new dependencies. - -{% callout type="caution" title="Using Bower?" %} -For your own project, you’ll need to switch to NPM if you’re using another package manager like bower. [Learn more about switching away from bower](https://bower.io/blog/2017/how-to-migrate-away-from-bower/) -{% /callout %} - -## Migrating application code - -This Angular application that you generated has the configuration that you need, but you don’t need any of its application code. You’ll replace that with the RealWorld app code. Delete the contents of `apps/realworld/src/app`. - -Starting in the `js` folder of the realworld app, copy all of the application code into `apps/realworld/src/app`. The resulting file tree should look like this: - -```text -apps -|____realworld-e2e -|____realworld -| |____src -| | |____app -| | | |____settings -| | | |____home -| | | |____config -| | | |____auth -| | | |____layout -| | | |____components -| | | |____profile -| | | |____article -| | | |____services -| | | |____editor -| | | |____app.js -| | |____assets -| | |____environments -| | |____favicon.ico -| | |____index.html -| | |____main.ts -| | |____polyfills.ts -| | |____styles.css -| | |____test.ts -``` - -{% callout type="warning" title="Javscript vs Typescript" %} -You most likely have your own AngularJS project written in JavaScript as well. While you’ll continue to use JavaScript through the rest of this example, we strongly recommend switching AngularJS projects to TypeScript, especially if you’re planning an upgrade to Angular. -{% /callout %} - -## Modifying index.html and main.ts - -Your generated application will also have an `index.html` provided. However, it’s set up for an Angular application, not an AngularJS application. Replace the contents of `apps/realworld/src/index.html` with the `index.html` from the RealWorld app. - -Your application also has a `main.ts` file which is responsible for bootstrapping your app. Again, you don’t need much from this file any more. Replace its contents with: - -```typescript -import './app/app.js'; -``` - -And re-name it to `main.js`. This will import the existing app.js file from the RealWorld app which will bootstrap the app. - -## Adding existing build and serve processes - -If you’re looking at the example repo, the code for this section is available on branch `initial-migration`. This section is an interim step that continues to use gulp to build and serve the app locally, so we can validate everything works before continuing with the migration. You’ll replace gulp in the next section. - -{% callout type="warning" title="Tools & node versions" %} -The RealWorld app uses gulp 3.9.1 to build. This version is not supported anymore and doesn’t run on any version of Node greater than 10.\*. To build this app using gulp, you need to install an appropriate version of Node and make sure you re-install your dependencies. If this isn’t possible (or you just don’t want to), feel free to skip to the next section. The webpack build process should run in any modern Node version. -{% /callout %} - -The RealWorld app uses gulp to build the application, as well as provide a development server. To verify that the migration has worked, stay with that build process for now. - -{% callout type="caution" title="Verify your changes" %} -During migration, you should take a small step and confirm that things work before moving ahead. Stopping and checking to see that your app still builds and functions is essential to a successful migration. -{% /callout %} - -Copy the `gulpfile.js` over from the RealWorld app and put it in `apps/realworld`. This is where all configuration files reside for the application. Make some adjustments to this file so that your build artifacts land in a different place. In an Nx workspace, all build artifacts should be sent to an app-specific folder in the `dist` folder at the root of your workspace. Your `gulpfile.js` should look like this: - -```javascript {% fileName="gulpfile.js" %} -var gulp = require('gulp'); -var notify = require('gulp-notify'); -var source = require('vinyl-source-stream'); -var browserify = require('browserify'); -var babelify = require('babelify'); -var ngAnnotate = require('browserify-ngannotate'); -var browserSync = require('browser-sync').create(); -var rename = require('gulp-rename'); -var templateCache = require('gulp-angular-templatecache'); -var uglify = require('gulp-uglify'); -var merge = require('merge-stream'); - -// Where our files are located -var jsFiles = 'src/app/**/*.js'; -var viewFiles = 'src/app/**/*.html'; - -var interceptErrors = function (error) { - var args = Array.prototype.slice.call(arguments); - - // Send error to notification center with gulp-notify - notify - .onError({ - title: 'Compile Error', - message: '<%= error.message %>', - }) - .apply(this, args); - - // Keep gulp from hanging on this task - this.emit('end'); -}; - -gulp.task('browserify', ['views'], function () { - return ( - browserify('./src/main.js') - .transform(babelify, { presets: ['es2015'] }) - .transform(ngAnnotate) - .bundle() - .on('error', interceptErrors) - //Pass desired output filename to vinyl-source-stream - .pipe(source('main.js')) - // Start piping stream to tasks! - .pipe(gulp.dest('../../dist/apps/realworld/')) - ); -}); - -gulp.task('html', function () { - return gulp - .src('src/index.html') - .on('error', interceptErrors) - .pipe(gulp.dest('../../dist/apps/realworld/')); -}); - -gulp.task('views', function () { - return gulp - .src(viewFiles) - .pipe( - templateCache({ - standalone: true, - }) - ) - .on('error', interceptErrors) - .pipe(rename('app.templates.js')) - .pipe(gulp.dest('src/app/config')); -}); - -// This task is used for building production ready -// minified JS/CSS files into the dist/ folder -gulp.task('build', ['html', 'browserify'], function () { - var html = gulp - .src('../../dist/apps/realworld/index.html') - .pipe(gulp.dest('../../dist/apps/realworld/')); - - var js = gulp - .src('../../dist/apps/realworld/main.js') - .pipe(uglify()) - .pipe(gulp.dest('../../dist/apps/realworld/')); - - return merge(html, js); -}); - -gulp.task('default', ['html', 'browserify'], function () { - browserSync.init(['../../dist/apps/realworld/**/**.**'], { - server: '../../dist/apps/realworld', - port: 4000, - notify: false, - ui: { - port: 4001, - }, - }); - - gulp.watch('src/index.html', ['html']); - gulp.watch(viewFiles, ['views']); - gulp.watch(jsFiles, ['browserify']); -}); -``` - -You need to point your `build` and `serve` tasks at this gulp build process. Typically, an Angular app is built using the Angular CLI, but the Angular CLI doesn’t know how to build AngularJS projects. All of your tasks are configured in the project configuration file `apps/realworld/project.json`. Find the `build` and `serve` tasks and replace them with this code block: - -```jsonc {% fileName="apps/realworld/project.json" %} -... - "build": { - "executor": "nx:run-commands", - "options": { - "commands": [ - { - "command": "npx gulp --gulpfile apps/realworld/gulpfile.js build" - } - ] - } - }, - "serve": { - "executor": "nx:run-commands", - "options": { - "commands": [ - { - "command": "npx gulp --gulpfile apps/realworld/gulpfile.js" - } - ] - } - }, -... -``` - -This sets up the `build` and `serve` commands to use the locally installed version of gulp to run `build` and `serve`. To see the RealWorld app working, run: - -```shell -nx serve realworld -``` - -Navigate around the application and see that things work. - -{% callout type="warning" title="Not using Gulp" %} -Your own project might not be using gulp. If you’re using webpack, you can follow the next section and substitute your own webpack configuration. If you’re using something else like grunt or a home-grown solution, you can follow the same steps here to use it. You’ll use the `run-commands` executor and substitute in the commands for your project. -{% /callout %} - -## Switching to webpack - -So far, you’ve mostly gotten already existing code and processes to work. This is the best way to get started with any migration: get existing code to work before you start making changes. This gives you a good, stable base to build on. It also means you have working code now rather than hoping you’ll have working code in the future! - -But migrating AngularJS code means we need to switch some of our tools to a more modern tool stack. Specifically, using webpack and babel is going to allow us to take advantage of Nx more easily. Becoming an expert in these build tools is outside the scope of this article, but I’ll address some AngularJS specific concerns. To get started, install these new dependencies: - -{% tabs %} -{% tab label="npm" %} - -```shell -npm add -D babel-plugin-angularjs-annotate -nx add @nx/web -``` - -{% /tab %} -{% tab label="yarn" %} - -```shell -yarn add -D babel-plugin-angularjs-annotate -nx add @nx/web -``` - -{% /tab %} -{% tab label="pnpm" %} - -```shell -pnpm add -D babel-plugin-angularjs-annotate -nx add @nx/web -``` - -{% /tab %} -{% /tabs %} - -Nx already has most of what you need for webpack added as a dependency. `@nx/web` contains the [executors](/concepts/executors-and-configurations) we need to use to build and serve the application with webpack and -`babel-plugin-angularjs-annotate` is going to accomplish the same thing that `browserify-ngannotate` previously did in gulp: add dependency injection annotations. - -Start with a `webpack.config.js` file in your application’s root directory: - -```javascript -const path = require('path'); - -module.exports = (config, context) => { - return { - ...config, - module: { - strictExportPresence: true, - rules: [ - { - test: /\.html$/, - use: [{ loader: 'raw-loader' }], - }, - // Load js files through Babel - { - test: /\.(js|jsx)$/, - loader: 'babel-loader', - options: { - presets: ['@babel/preset-env'], - plugins: ['angularjs-annotate'], - }, - }, - ], - }, - }; -}; -``` - -{% callout type="note" title="Webpack configuration" %} -This webpack configuration is deliberately simplified for this tutorial. A real production-ready webpack config for your project will be much more involved. See [this project](https://github.com/preboot/angularjs-webpack) for an example. -{% /callout %} - -To use webpack instead of gulp, go back to your `apps/realworld/project.json` file and modify the `build` and `serve` commands again: - -```jsonc {% fileName="apps/realworld/project.json" %} -... -"build": { - "executor": "@nx/web:webpack", - "options": { - "outputPath": "dist/apps/realworld", - "index": "apps/realworld/src/index.html", - "main": "apps/realworld/src/main.js", - "polyfills": "apps/realworld/src/polyfills.ts", - "tsConfig": "apps/realworld/tsconfig.app.json", - "assets": [ - "apps/realworld/src/favicon.ico", - "apps/realworld/src/assets" - ], - "styles": ["apps/realworld/src/styles.css"], - "scripts": [], - "webpackConfig": "apps/realworld/webpack.config", - "buildLibsFromSource": true - }, - "configurations": { - "production": { - "fileReplacements": [ - { - "replace": "apps/realworld/src/environments/environment.ts", - "with": "apps/realworld/src/environments/environment.prod.ts" - } - ], - "optimization": true, - "outputHashing": "all", - "sourceMap": false, - "extractCss": true, - "namedChunks": false, - "extractLicenses": true, - "vendorChunk": false, - "budgets": [ - { - "type": "initial", - "maximumWarning": "2mb", - "maximumError": "5mb" - } - ] - } - } -}, -"serve": { - "executor": "@nx/web:dev-server", - "options": { - "buildTarget": "realworld:build" - } -}, -... -``` - -You may have noticed a rule for loading HTML in `webpack.config.js`. You need to modify some of your AngularJS code to load HTML differently. The application previously used the template cache to store all of the component templates in code, rather than download them at run time. This works, but you can do things a little better with webpack. - -Rather than assigning `templateUrl` for your components, you can instead import the HTML and assign it to the `template` attribute. This is effectively the same as writing your templates in-line, but you still have the benefit of having a separate HTML file. The advantage is that the template is tied to its component, not a global module like the template cache. Loading all templates into the template cache is more performant than individually downloading templates, but it also means your user is downloading every single component’s template as part of start-up. This was fine in AngularJS when you didn’t easily have access to lazy-loading, so you always had a large up-front download cost. As you begin to upgrade to Angular or other modern frontend frameworks, you will gain access to lazy-loading: only loading code when it’s necessary. By making this change now, you set yourself up for success later. - -To accomplish this, open `config/app.config.js` which is the main app component. Modify it like this: - -```javascript -import authInterceptor from './auth.interceptor'; -import template from '../layout/app-view.html'; - -function AppConfig( - $httpProvider, - $stateProvider, - $locationProvider, - $urlRouterProvider -) { - 'ngInject'; - - $httpProvider.interceptors.push(authInterceptor); - - /* - If you don't want hashbang routing, uncomment this line. - Our tutorial will be using hashbang routing though :) - */ - // $locationProvider.html5Mode(true); - - $stateProvider.state('app', { - abstract: true, - template, - resolve: { - auth: function (User) { - return User.verifyAuth(); - }, - }, - }); - - $urlRouterProvider.otherwise('/'); -} - -export default AppConfig; -``` - -This change loads the HTML code directly and sets it to the template attribute of the component. The HTML rule that you specified in the webpack config will take care of loading the HTML correctly and adding it to the template like this. - -Now, go through each component of the application and make this change. To make sure that you’ve really modified every component correctly, delete the template cache file (`config/app.templates.js`) that gulp generated earlier. - -{% callout type="check" title="Automate the work" %} -In an example like this, it’s easy enough to make this kind of change by hand. In a larger codebase, doing this manually could be very time-intensive. You’ll want to look into an automated tool to do this for you, such as js-codemod or generators. -{% /callou %} - -We also need to modify the `app.js` and remove the import of `config/app.templates.js`. Modify it like this: - -```javascript {% fileName="config/app.templates.js" %} -import angular from 'angular'; - -// Import our app config files -import constants from './config/app.constants'; -import appConfig from './config/app.config'; -import appRun from './config/app.run'; -import 'angular-ui-router'; -// Import our app functionaity -import './layout'; -import './components'; -import './home'; -import './profile'; -import './article'; -import './services'; -import './auth'; -import './settings'; -import './editor'; - -// Create and bootstrap application -const requires = [ - 'ui.router', - 'app.layout', - 'app.components', - 'app.home', - 'app.profile', - 'app.article', - 'app.services', - 'app.auth', - 'app.settings', - 'app.editor', -]; - -// Mount on window for testing -window.app = angular.module('app', requires); - -angular.module('app').constant('AppConstants', constants); - -angular.module('app').config(appConfig); - -angular.module('app').run(appRun); - -angular.bootstrap(document, ['app'], { - strictDi: true, -}); -``` - -Run the application the same way as before: - -```shell -nx serve realworld -``` - -## Unit testing - -Unit testing can be an important part of any code migration. If you migrate your code into a new system, and all of your unit tests pass, you have a higher degree of confidence that your application actually works without manually testing. Unfortunately, the RealWorld application doesn’t have any unit tests, but you can add your own. - -You need a few dependencies for AngularJS unit testing that Nx doesn’t provide by default: - -{% tabs %} -{% tab label="npm" %} - -```shell -npm add -D angular-mocks@1.5.11 karma-webpack -``` - -{% /tab %} -{% tab label="yarn" %} - -```shell -yarn add -D angular-mocks@1.5.11 karma-webpack -``` - -{% /tab %} -{% tab label="pnpm" %} - -```shell -pnpm add -D angular-mocks@1.5.11 karma-webpack -``` - -{% /tab %} -{% /tabs %} - -Earlier, you configured this app to use Karma as its unit test runner. Nx has provided a Karma config file for you, but you’ll need to modify it to work with AngularJS: - -```javascript -const webpack = require('./webpack.config'); -const getBaseKarmaConfig = require('../../karma.conf'); - -module.exports = function (config) { - const baseConfig = getBaseKarmaConfig(); - config.set({ - ...baseConfig, - frameworks: ['jasmine'], - plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('karma-coverage-istanbul-reporter'), - require('karma-webpack'), - ], - // This will be the new entry to webpack - // so it should just be a single file - files: ['src/test.js'], - - // Preprocess test index and test files using - // webpack (will run babel) - preprocessors: { - 'src/test.js': ['webpack'], - 'src/**/*.spec.js': ['webpack'], - }, - - // Reference webpack config (single object) - // and configure some middleware settings - webpack: { - ...webpack({}), - mode: 'development', - }, - webpackMiddleware: { - noInfo: true, - stats: 'errors-only', - }, - - // Typical Karma settings, see docs - reporters: ['progress'], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - browsers: ['ChromeHeadless'], - singleRun: true, - concurrency: Infinity, - }); -}; -``` - -Next, rename the existing `apps/realworld/src/test.ts` to `apps/realworld/src/test.js` and modify its content to match the following: - -```javascript {% fileName="apps/realworld/src/test.js" %} -import 'angular'; -import 'angular-mocks'; -import 'angular-ui-router'; - -// require all test files using special Webpack feature -// https://webpack.github.io/docs/context.html#require-context -const testsContext = require.context('./', true, /\.spec$/); - -testsContext.keys().forEach(testsContext); -``` - -Now add a unit test for the comment component: - -```javascript -import articleModule from './index'; - -beforeEach(() => { - // Create the module where our functionality can attach to - angular.mock.module('ui.router'); - angular.mock.module(articleModule.name); -}); - -let component; - -beforeEach( - angular.mock.inject(($rootScope, $componentController) => { - let User = { - current: false, - }; - component = $componentController('comment', { User }); - }) -); - -describe('comment component', () => { - it('should be defined', () => { - expect(component).toBeDefined(); - }); - - it('should default canModify to false', () => { - expect(component.canModify).toEqual(false); - }); -}); -``` - -This unit test does a check to make sure the component compiles and that it sets default permissions correctly. - -To run the unit tests: - -```shell -nx test -``` - -You should see green text as your test passes. - -![Unit tests passing](/shared/migration/migration-angularjs-unit-tests-passing.png) - -## End to End testing - -End to End (or E2E) testing is another important part of any migration. The more tests you have to verify your code, the easier it is to confirm that your code works the same way it did before. Again, the realworld application doesn’t have any e2e tests, so you need to add your own. - -Nx created `realworld-e2e` for you when you generated your app. There is an example test already generated in `apps/realworld-e2e/src/app.e2e-spec.ts`. It has a helper file named `app.po.ts`. The `spec` file contains the actual tests, while the `po` file contains helper functions to retrieve information about the page. The generated test checks to make sure the title of the app is displayed properly, an indication that the app bootstrapped properly in the browser. - -You need to modify these files to account for the RealWorld app layout. Make the following modifications: - -```typescript {% fileName="app.e2e-spec.ts" %} -import { AppPage } from './app.po'; -import { browser, logging } from 'protractor'; - -describe('workspace-project App', () => { - let page: AppPage; - - beforeEach(() => { - page = new AppPage(); - }); - - it('should display app title', async () => { - await page.navigateTo(); - - expect(await page.getTitleText()).toEqual('conduit'); - }); - - afterEach(async () => { - // Assert that there are no errors emitted from the browser - const logs = await browser.manage().logs().get(logging.Type.BROWSER); - expect(logs).not.toContain( - jasmine.objectContaining({ - level: logging.Level.SEVERE, - } as logging.Entry) - ); - }); -}); -``` - -```typescript {% fileName="app.po.ts" %} -import { browser, by, element } from 'protractor'; - -export class AppPage { - navigateTo(): Promise { - return browser.get(browser.baseUrl) as Promise; - } - - getTitleText(): Promise { - return element(by.css('h1.logo-font')).getText() as Promise; - } -} -``` - -You also need to modify the project configuration of the `realworld-e2e` app at `apps/realworld-e2e/project.json`. This will point your e2e process at the `development` configuration of the `realworld` app by default. - -```jsonc {% fileName="apps/realworld-e2e/project.json" %} -{ - ... - "e2e": { - "executor": "@angular-devkit/build-angular:protractor", - "options": { - "protractorConfig": "apps/realworld-e2e/protractor.conf.js", - "devServerTarget": "realworld:serve" - }, - "configurations": { - "production": { - "devServerTarget": "realworld:serve:production" - } - } - }, -} -``` - -To run e2e tests, use the `e2e` command: - -```shell -nx e2e realworld-e2e -``` - -You should see a browser pop up to run the Protractor tests and then green success text in your console. - -## Summary - -- Nx workspaces can be customized to support AngularJS projects -- AngularJS projects can be migrated into an Nx workspace using existing build and serve processes -- Switching to Webpack can enable your Angular upgrade success later -- Unit testing and e2e testing can be used on AngularJS projects and can help ensure a successful migration diff --git a/docs/shared/packages/angular/angular-plugin.md b/docs/shared/packages/angular/angular-plugin.md index 32929376da..5ddbaf8e0e 100644 --- a/docs/shared/packages/angular/angular-plugin.md +++ b/docs/shared/packages/angular/angular-plugin.md @@ -10,7 +10,6 @@ within an Nx workspace. It provides: - Generators to help scaffold code quickly, including: - Micro Frontends - Libraries, both internal to your codebase and publishable to npm - - Upgrading AngularJS applications - Single Component Application Modules (SCAMs) - NgRx helpers. - Utilities for automatic workspace refactoring. @@ -123,5 +122,4 @@ nx g @nx/angular:service my-service - [Angular Monorepo Tutorial](/getting-started/tutorials/angular-monorepo-tutorial) - [Migrating from the Angular CLI](/recipes/angular/migration/angular) - [Setup Module Federation with Angular and Nx](/concepts/module-federation/faster-builds-with-module-federation) -- [Upgrading an AngularJS application to Angular](/recipes/angular/migration/angularjs) - [Using Tailwind CSS with Angular projects](/recipes/angular/using-tailwind-css-with-angular-projects) diff --git a/docs/shared/reference/sitemap.md b/docs/shared/reference/sitemap.md index 252ef86386..10fd4b62e5 100644 --- a/docs/shared/reference/sitemap.md +++ b/docs/shared/reference/sitemap.md @@ -92,7 +92,6 @@ - [Migrating from Angular CLI](/recipes/angular/migration/angular) - [Migrating From Multiple Angular CLI Repos](/recipes/angular/migration/angular-multiple) - [Migrating Angular Application manually](/recipes/angular/migration/angular-manual) - - [Migrating from AngularJS](/recipes/angular/migration/angularjs) - [Use Environment Variables in Angular](/recipes/angular/use-environment-variables-in-angular) - [Using Tailwind CSS with Angular projects](/recipes/angular/using-tailwind-css-with-angular-projects) - [Setup Module Federation with SSR for Angular](/recipes/angular/module-federation-with-ssr) diff --git a/nx-dev/nx-dev/redirect-rules.js b/nx-dev/nx-dev/redirect-rules.js index b4353eb88a..da6050e611 100644 --- a/nx-dev/nx-dev/redirect-rules.js +++ b/nx-dev/nx-dev/redirect-rules.js @@ -219,7 +219,9 @@ const diataxis = { '/migration/adding-to-monorepo': '/recipes/adopting-nx/adding-to-monorepo', '/migration/migration-cra': '/recipes/adopting-nx/migration-cra', '/migration/migration-angular': '/recipes/adopting-nx/migration-angular', - '/migration/migration-angularjs': '/recipes/adopting-nx/migration-angularjs', + '/migration/migration-angularjs': '/recipes/adopting-nx/migration-angular', + '/recipes/angular/migration/angularjs': + '/recipes/adopting-nx/migration-angular', '/migration/preserving-git-histories': '/recipes/adopting-nx/preserving-git-histories', '/migration/manual': '/recipes/adopting-nx/manual', diff --git a/packages-legacy/angular/package.json b/packages-legacy/angular/package.json index a2e99cdbbf..908c00c395 100644 --- a/packages-legacy/angular/package.json +++ b/packages-legacy/angular/package.json @@ -2,7 +2,7 @@ "name": "@nrwl/angular", "version": "0.0.1", "private": false, - "description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.", + "description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.", "repository": { "type": "git", "url": "https://github.com/nrwl/nx.git", diff --git a/packages/angular/package.json b/packages/angular/package.json index eabda4446a..c222f898f4 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -2,7 +2,7 @@ "name": "@nx/angular", "version": "0.0.1", "private": false, - "description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.", + "description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.", "repository": { "type": "git", "url": "https://github.com/nrwl/nx.git",