I inherited a project that was quickly put together as a proof of concept/demo that was created using the Yeoman Angular-generator. While this was the preferred way of setting up projects a few years ago, front end technologies and patterns have moved on. Having recently come from a project that abandoned bower, gulp, grunt, and moved to ES6, JSPM, npm, and node scripts I knew I wanted to develop using ES6 and more importantly use a module loader.
Next, we want to add a webpack.config.js file at our root directory. Yours may need to be modified for testing, path directories, and other project specific requirements.
Next we need an entry point for our application that will import your top level dependencies. Ideally each component would import ONLY the dependencies that they require, but to get up and running quickly we can create a bootstrap file to import everything. We will also want to create module files for each module in your application. My application only has one module portfolioApp, but if yours has more you will do this for each module and then import those modules as dependencies to your top level module.
So my app-module.js file looks like this:
We now need to update our components to remove the dependencies on angular. Ideally we would break the app into components, but I will leave that for a later exercise. So for now I will have a controller-module.js, service-module.js, and view-module.js. All of our components will be imported here and registered as angular controllers, views, etc. We also then need to update the component files to export the component to be registered. Here is an example of one:
This is more of an optional step, so you could still use the native angular templating, but I have grown accustomed to importing templates as strings and attaching them to their components. This is an intermediate step to that. We are going to create the `app-templates.js` that was mentioned in step #4.
Since I was starting the project fresh, I decided that removing grunt and bower would be a good way of understanding the codebase, a good chance to learn Webpack, and finally to make sure that we were using the best practices in our front end code. In the end the transition was fairly painless and made the code more in line with current practices.
I've decided to do this with a website that I developed using the yeomen generator to show you the basic steps of converting such a project. The website is live here: http://andy-meyers.appspot.com/#/, the yeoman generated repo is here https://github.com/thatguyandy27/Portfolio/tree/angular-yeoman, and the ES6ified repo is here: https://github.com/thatguyandy27/Portfolio/tree/es2016.
So let's get started.
1. Install Webpack and Other Loaders
The first thing we are going to do is add what we need to get webpack up and running using npm scripts. Each one of these can be added using `npm install --save-dev <packagename>`. Here are a list of the packages and their uses. You may need more or less depending on your repo, like unit for unit testing, but this is the core stuff to get started.
- autoprefixer: Parse CSS and add vendor prefixes to rules by
- babel-core: Babel compiler core
- babel-loader: Webpack plugin for Babel
- babel-preset-es2015: Babel preset for all es2015 plugins.
- copy-webpack-plugin: Copy files and directories in webpack
- css-loader: Css module for webpack
- extract-text-webpack-plugin: Extract text from bundle into a file.
- file-loader: File loader for webpack
- html-webpack-plugin: simplifies creation of HTML files to serve your webpack bundles
- node-sass: sass for node
- postcss-loader: PostCSS loader for webpack
- raw-loader: raw loader module for webpack
- sass-loader: sass loader module for webpack
- style-loader: style loader for webpack
- webpack: Webpack
- webpack-dev-server: Web server to serve a webpack bundle
2. Create New Config Files
Next, we want to add a webpack.config.js file at our root directory. Yours may need to be modified for testing, path directories, and other project specific requirements.
We will also need to add a .babelrc file in our root that we will just populate with `{ "presets":["es2015"] }`.
This should be pretty straight forward. For each bower package you should be able to add an npm package using `npm install --save <packagename>`. After you are done we can delete the bower.json file and the bower dependencies folder.
3. Update Your Bower Dependencies to NPM Dependencies.
This should be pretty straight forward. For each bower package you should be able to add an npm package using `npm install --save <packagename>`. After you are done we can delete the bower.json file and the bower dependencies folder.
4. Create A Top Module File For the App
Next we need an entry point for our application that will import your top level dependencies. Ideally each component would import ONLY the dependencies that they require, but to get up and running quickly we can create a bootstrap file to import everything. We will also want to create module files for each module in your application. My application only has one module portfolioApp, but if yours has more you will do this for each module and then import those modules as dependencies to your top level module.
So my app-module.js file looks like this:
import angular from 'angular'; import 'angular-animate'; import 'angular-cookies'; import 'angular-resource'; import 'angular-route'; import 'angular-sanitize'; import 'angular-touch'; import appTemplateConfig from './app-templates.js'; const module = angular .module('portfolioApp', [ 'ngAnimate', 'ngCookies', 'ngResource', 'ngRoute', 'ngSanitize', 'ngTouch' ]).run(appTemplateConfig); export default module;
So a quick review of what is going on here. First, I am importing the angular npm packages dependencies using the es6 syntax. We then create our module and export it to be used later to register our controllers, services, components, etc. I am also importing an appTemplateConfig function that will be run on load of the application. We will get into this more later.
5. Change JS Files to Import & Export
We now need to update our components to remove the dependencies on angular. Ideally we would break the app into components, but I will leave that for a later exercise. So for now I will have a controller-module.js, service-module.js, and view-module.js. All of our components will be imported here and registered as angular controllers, views, etc. We also then need to update the component files to export the component to be registered. Here is an example of one:
import THREE from 'three/three.js';const ThreeJSPlanetCtrl = function($scope, planetService){...};ThreeJSPlanetCtrl.$inject = ['$scope', 'planetService'];export default ThreeJSPlanetCtrl;
The important thing here is that I didn't have to change most of the code. I now import any non-angular dependencies using the import syntax and use it as normal. Finally at the end of the controller I use `$inject` syntax instead using something like ng-annotate. We finally then export the function to be registered later on our module that we previously created. Here is a controller-module.js that I created that will be used to register all of my controllers.
import '../libraries/OrbitControls.js'; import AboutCtrl from './about.js'; import BubblePopperCtrl from './bubblePopper.js'; import CanvasChessCtrl from './canvasChess.js'; import CssSolarSystemCtrl from './csssolarsystem.js'; import RelationshipEditorGOTCtrl from './relationshipEditorGOT.js'; import RelationshipMapGOTCtrl from './relationshipMapGOT.js'; import ThreeJSBubbleCtrl from './threejsBubbles.js'; import ThreeJSDemoCtrl from './threejsDemo.js'; import ThreeJSPlanetCtrl from './threejsPlanet.js'; import ThreeJSSolarSystemCtrl from './threejsSolarSystem.js'; import TransparentImageDemoCtrl from './transparentImageDemo.js'; import appModule from '../../app-module.js'; appModule.controller('threejsPlanetCtrl', ThreeJSPlanetCtrl) .controller('threeJsSolarSystemCtrl', ThreeJSSolarSystemCtrl) .controller('transparentImageDemoCtrl', TransparentImageDemoCtrl) .controller('AboutCtrl', AboutCtrl) .controller('bubblePopperCtrl', BubblePopperCtrl) .controller('CanvasChessCtrl', CanvasChessCtrl) .controller('CssSolarSystemCtrl', CssSolarSystemCtrl) .controller('relationshipEditorGOTCtrl', RelationshipEditorGOTCtrl) .controller('relationshipMapGOTCtrl', RelationshipMapGOTCtrl) .controller('threeJsBubbleCtrl', ThreeJSBubbleCtrl) .controller('threeJsDemoCtrl', ThreeJSDemoCtrl);
This file imports the appModule that we defined in step #4 and then register every controller we have as an angular controller. We will perform this step as many times as necessary depending on how your app is structured.
6. Create A Template Cache File
This is more of an optional step, so you could still use the native angular templating, but I have grown accustomed to importing templates as strings and attaching them to their components. This is an intermediate step to that. We are going to create the `app-templates.js` that was mentioned in step #4.
import mainTemplate from './views/main.html'; import aboutTemplate from './views/about.html'; import bubblePopperTemplate from './views/bubblePopper.html'; import canvasChessTemplate from './views/canvasChess.html'; import cssSolarSystemTemplate from './views/cssSolarSystem.html'; import relationshipEditorGOTTemplate from './views/relationshipEditorGOT.html'; import relationshipMapGOTTemplate from './views/relationshipMapGOT.html'; import threejsBubblesTemplate from './views/threejsBubbles.html'; import threejsDemoTemplate from './views/threejsDemo.html'; import threejsPlanetTemplate from './views/threejsPlanet.html'; import threeJsSolarSystemTemplate from './views/threeJsSolarSystem.html'; import transparentImageDemoTemplate from './views/transparentImageDemo.html'; function templateConfig($templateCache){ $templateCache.put('views/about.html', aboutTemplate); $templateCache.put('views/main.html', mainTemplate); $templateCache.put('views/bubblePopper.html', bubblePopperTemplate); $templateCache.put('views/canvasChess.html', canvasChessTemplate); $templateCache.put('views/cssSolarSystem.html', cssSolarSystemTemplate); $templateCache.put('views/relationshipEditorGOT.html', relationshipEditorGOTTemplate); $templateCache.put('views/relationshipMapGOT.html', relationshipMapGOTTemplate); $templateCache.put('views/threejsBubbles.html', threejsBubblesTemplate); $templateCache.put('views/threejsDemo.html', threejsDemoTemplate); $templateCache.put('views/threejsPlanet.html', threejsPlanetTemplate); $templateCache.put('views/threeJsSolarSystem.html', threeJsSolarSystemTemplate); $templateCache.put('views/transparentImageDemo.html', transparentImageDemoTemplate); } templateConfig.$inject = ['$templateCache']; export default templateConfig;
All we are doing in this file is creating a function that has $templateCache injected into it and importing those templates to those paths that we are already using in the app. We then export the function to be run in when the app loads. This moves the importing of the templates from runtime to build time as the strings will be in the file already.
In my webpack.config I have defined an entry point to the application that will bootstrap the entire thing. Here is the snippet from the config.
We are going to add some basic npm scripts that can be run in place of grunt, gulp, etc. These will all live in your package.js and can be run using npm run <command>. Here are the scripts that I've added:
We now have the following commands:
Finally enjoy using your es6 syntax and start componentizing.
7. Create The App Entry File.
In my webpack.config I have defined an entry point to the application that will bootstrap the entire thing. Here is the snippet from the config.
config.entry = isTest ? {} : { ['app']: './app/app-main.js' };
This tells the app to use this file to bootstrap everything. It will pull in the top level dependencies which will then pull in their dependencies all the way down the tree.
This file is pretty simple. Here is what it looks like:
This file is pretty simple. Here is what it looks like:
import './styles/about.scss'; import './styles/cssSolarSystem.scss'; import './styles/main.scss'; import './styles/relationshipEditorGOT.scss'; import './styles/relationshipMapGOT.scss'; import './styles/threejsPlanet.scss'; import './styles/transparentImageDemo.scss'; import './scripts/app.js'; import './scripts/services/service-module.js'; import './scripts/controllers/controller-module.js'; import './scripts/root.js'; import './scripts/libraries/OrbitControls.js';It is just an import of the css styles and the top level application files. If you have things that need to be run in order for some reason you can change that order here as well.
8. Create Some NPM Scripts
We are going to add some basic npm scripts that can be run in place of grunt, gulp, etc. These will all live in your package.js and can be run using npm run <command>. Here are the scripts that I've added:
"scripts": { "build": "rimraf dist && webpack --bail --progress --profile", "server": "webpack-dev-server -d --history-api-fallback --inline --progress --port 9000", "test": "karma start test/karma.conf.js", "start": "npm run server" }
We now have the following commands:
- build: removes the dist directory and then tells webpack to build
- server: runs webpack locally in dev mode
- test: runs the karma tests
- start: shorthand for server
You may need to add other commands as your setup may be different. The important thing to note is you can use these commands to execute others. So if your process has 4 steps you can have 4 indvidual commands and then one to call them in order.
9. Enjoy
Finally enjoy using your es6 syntax and start componentizing.
No comments:
Post a Comment