Migrating a project to Angular CLI build system
12 Aug 2017I recently migrated an Angular app from a gulp build system to an Angular CLI (version 1.2.6). I am enjoying the new build system and the improved Angular performance. I wanted to share the process, some pain points and lessons learned.
A good starting point for migrating is the angular-cli migration guide and moving guide. Also check out the list of general stories
Our migration plan was a little more involved since our project consists of an Angular client app and a backend node typescript server with Express.js to serve our client app and proxy an external API.
Migration Outline
- Create a new angular-cli project
- Copy over (move) your existing application into the new project
- Merge package.json files and resolve package versions
- Iterate over the app, build and tests
- Switch over build and run scripts
Goals
- Replace current gulp commands and code with angular-cli and npm scripts
- Work on both windows and mac development machines
- Be able to run a local server to host an API and client ap
- Create a shell script for build and packaging for the build server
Migration Preparation
- Move all static resources into the
assetsfolder and ensure relative paths - Ensure
nodeandnpmprereqs forangular-cli. I prefernvm. - Find and fix any private variables being referenced in components. Make those vars public.
Lessons Learned and Pain Points
-
I only had one global
tsconfig.jsonfile before but angular-cli makes use of multipletsconfig.jsonfiles and for a newtsconfig.app.jsonfile. Also, as of right now some editors ( VS Code #24362) don’t support custom tsconfig filenames yet. In some cases I had to create atsconfig.jsonfor VS Code alongside thetsconfig.app.jsonfile in order for the editor to recognize specific paths. Not an ideal solution. -
I used to be to run
tscand compile both server and client apps at the same time. Unfortunately, with the cli I now have to compile the client and server apps separately and with different commands and config. -
If you are going to ditch gulp entirely you will need to embrace
npm scriptsto augmentangular-cli. I found this resource “Why I Left Gulp and Grunt for npm Scripts” by Cory House a must read before the migration. -
It is important to realize that
ng serveandng testcompile your typescript into memory and NOT physical files. -
If you are clueless as to why
ng testdoesn’t work with cryptic errors look into thepollyfills.tsfile and start uncommenting lines. -
I would recommend locking package.json versions with exact version numbers, shrinkwrap, lockfile or even yarn. There is no joy in debugging
@types,tscversions and incompatible modules. -
Building with
ng build --prodwhich uses AOT compilation is a lot more strict than the default typescript rules withng serve. -
You may run into libraries that don’t support AOT compilation yet. For example, we had to switch from Alberplz/angular2-color-picker to zefoy/ngx-color-picker
-
In cases where you want to (not really necessary to remove just for the sake) but you can use plain old js node processes and wrap them in
npm scripts. -
Using
sassfor css is straightforward by setting"styleExt": "scss"in.angular-cli.json. See Angular2 - Angular-CLI SASS options -
Using
font awesomewas straightforward by simply adding"../node_modules/font-awesome/css/font-awesome.css"to thestylesarray in.angular-cli.jsonfile.
Serve and Build Scripts
Serving for development work
Just use npm start.
This compiles and serves the client app from memory.
Underneath the hood, serving our client and server requires some extra steps since we also need to compile a typescript server application. This gets tricky since ng serve is running its own lite http server. We can actually proxy some routes in the ng serve to our backend server running on a different port. This is available with a --proxy-config switch.
When we compile our client app with ng build we can actually just serve our node server and the compiled client since our app handles all the routing by design.
This is why there are some port number and proxy switching between the serve and build tasks.
Below are the baisc npm scripts we run in parallel in order to reduce the command into a simple npm start
"compile-server": "tsc -p ./src/server",
"server": "npm run compile-server && cross-env NODE_CONFIG_DIR=./src/server/config node dist/server/index.js",
"client": "ng serve --base-href=/ --proxy-config dev.proxy.conf.json -p 3000 --sourcemap=true --watch ",
"start": "cross-env NODE_PORT=1337 npm-run-all --parallel client server"
I make use of the cross platform cross-env and npm-run-all libraries to support macs and window machines.
Buliding for prod (Bundling and AOT)
The below table explains the difference between ng build --prod and ng serve (aka ng build)
| Flag | --dev |
--prod |
|---|---|---|
--aot |
false |
true |
--environment |
dev |
prod |
--output-hashing |
media |
all |
--sourcemaps |
true |
false |
--extract-css |
false |
true |
Run npm run start-build in order to compile the client and server apps into the dist directory and run a node server for that directoy. This is actually how we will in a deploy (production) mode.
Below are the package.json scripts:
"build": "ng build --prod --base-href=/app/ --watch",
"start-build": "cross-env NODE_ENV=dev NODE_PORT=3000 npm-run-all --parallel build server",
Running Tests
Use ng test or ng test --single-run --browsers PhantomJS for the build server and PhantomJS.
Build Script and Deployment Server
I won’t go into great depth regarding our build script but I will include it for the big picture. We use this on our Bamboo build server. It works but is rather inefficient.
In the end we want a my-client-x-x-x.zip with the following:
- compiled client bundles
- compiled server code
- node_modules required for server app
#!/bin/sh
# get build number and branch provided in bamboo task
build=$1
branch=$2
echo "build $build and branch $branch"
. ~/.nvm/nvm.sh
nvm use 7.2.0
echo 'node -v'
node -v
npm install -g npm@5.2.0 --no-progress
echo 'npm -v'
npm -v
echo 'npm install'
npm install --no-progress
echo 'npm install finished'
echo 'showing versions with npm ls'
npm ls
# Dump versions for logs
ng version
npm run clean
npm run compile-server
npm run compile-lib
npm run test-build-server
# remove current dist folder files
echo 'Removing current ./dist folder'
rm -rf ./dist
mkdir ./dist
rm -rf artifacts
mkdir -p artifacts
# Creates dist/app files
echo 'Running ng build --prod'
ng build --prod --progress=false
echo 'ng build finished'
echo 'optomize build images'
node ./devops/scripts/min-images.js
# Creates dist/server files. we have to compile server tsc separately
cd src/server
# run tsc from bin modules to control our tsc version with package.json
../../node_modules/.bin/tsc
# back to root proj directory
cd ../../
echo 'copy server config directory'
cp -rf ./src/server/config ./dist/server
echo 'Run devops/version.it.js to get version and bamboo build'
# Create version.json and bamboo vars text file
version=$(node devops/version-it.js $build $branch)
echo 'npm prune --production'
npm prune --production
# copy over to dist
cp -rf ./node_modules ./dist/node_modules
zipFileName=my-client-$version.zip
zip -r "./artifacts/$zipFileName" dist devops
echo "Created ZIP file for dist at ${zipFileName}"
Questions and Next Steps
- How to force devs to verify
ng build --prodbefore committing? - How to use AOT compilation when using
ng test?