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
assets
folder and ensure relative paths - Ensure
node
andnpm
prereqs 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.json
file before but angular-cli makes use of multipletsconfig.json
files and for a newtsconfig.app.json
file. 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.json
for VS Code alongside thetsconfig.app.json
file in order for the editor to recognize specific paths. Not an ideal solution.I used to be to run
tsc
and 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 scripts
to 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 serve
andng test
compile your typescript into memory and NOT physical files.If you are clueless as to why
ng test
doesn't work with cryptic errors look into thepollyfills.ts
file 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
,tsc
versions and incompatible modules.Building with
ng build --prod
which 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
sass
for css is straightforward by setting"styleExt": "scss"
in.angular-cli.json
. See Angular2 - Angular-CLI SASS optionsUsing
font awesome
was straightforward by simply adding"../node_modules/font-awesome/css/font-awesome.css"
to thestyles
array in.angular-cli.json
file.
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 --prod
before committing? - How to use AOT compilation when using
ng test
?