├── .gitignore ├── README.md ├── content ├── Angular-Template-Cache.md ├── Authoring-libraries.md ├── Automatic-CSS-refresh.md ├── Automatic-browser-refresh.md ├── Basics.md ├── CSS,-Fonts,-Images.md ├── Configuring-Angular-JS.md ├── Creating-a-dev-and-production-config.md ├── Custom-workflow-entry.md ├── Deploy-to-production.md ├── Getting-started.md ├── Go-JavaScript-next.md ├── Inlining-Fonts.md ├── Inlining-Images.md ├── Introduction-to-Angular-JS.md ├── Introduction-to-Webpack.md ├── Lazy-loaded-entry-points.md ├── Loading-CSS.md ├── Loading-HTML.md ├── Loading-LESS-or-SASS.md ├── Matchers.md ├── Multiple-entry-points.md ├── Optimizing-Caching.md ├── Optimizing-Development.md ├── Optimizing-Rebundling.md ├── Optimizing-Workflow.md ├── README.md ├── Requiring-Files.md ├── Requiring-Images-and-Fonts.md ├── Running-a-workflow.md ├── SUMMARY.md ├── Single-bundle.md ├── Split-app-and-vendors.md ├── Understanding-chunks.md ├── Wing-it-like-a-pro.md ├── Writing-Loaders.md └── _Footer.md ├── package.json └── scripts ├── deploy-gh-pages.js ├── deploy-wiki.sh ├── generate-wiki.js └── sync-wiki.sh /.gitignore: -------------------------------------------------------------------------------- 1 | gh-pages/ 2 | node_modules/ 3 | wiki/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-webpack-cookbook - A cookbook for using Webpack with Angular 2 | 3 | Go to [cookbook](https://dmachat.github.io/angular-webpack-cookbook) 4 | 5 | # Contributing 6 | If you notice something to improve, the easiest way to do that is to 7 | 8 | 1. Fork this repo 9 | 2. Set up a branch 10 | 3. Make the changes (see `/content`) 11 | 4. Submit a PR 12 | 13 | It's just a regular Github PR flow. 14 | 15 | Alternatively you can [open an issue](https://github.com/dmachat/angular-webpack-cookbook/issues/new) and we'll look into it. 16 | 17 | Note that `gh-pages` branch and wiki content gets generated based on the main repository content. 18 | 19 | ## Gitbook Generator 20 | 21 | The generator converts the wiki content to Gitbook (standalone site). In this case it is pushed to `gh-pages`. Use it as follows: 22 | 23 | 1. `npm install` 24 | 2. `npm run generate-gitbook` 25 | 26 | This should generate `/gh-pages`. You can serve that directory through some static server (ie. hit `serve` at `/gh-pages`). 27 | 28 | It is possible to deploy the book by hitting `npm run deploy-gitbook`. This will update `gh-pages` branch. 29 | -------------------------------------------------------------------------------- /content/Angular-Template-Cache.md: -------------------------------------------------------------------------------- 1 | Another powerful tool at our disposal which is made easy by Webpack, is template caching. We saw how we can [require html files](Loading-HTML), and Webpack will convert them to JavaScript friendly strings, but leaves use with a potentially long static string injected in our modules at every point where we need them, and then parsed by our directive or controller. Instead, Angular provides us with the [$templateCache](https://docs.angularjs.org/api/ng/service/$templateCache) service, which allows us to register and pre-parse our templates, so they're reusable anywhere in our app, and faster to compile and interpolate onto the page. We'll use leverage [ngtemplate-loader](https://github.com/WearyMonkey/ngtemplate-loader) with Webpack to take full advantage of it. `npm install --save-dev ngtemplate-loader` will install the loader, and in your Webpack config file, test for html and apply the loader like this: 2 | 3 | ```js 4 | { 5 | test: /\.html$/, 6 | loader: 'ngtemplate!html' 7 | } 8 | ``` 9 | You can optionally use other html loaders like jade or handlebars in front of ngtemplate. ngtemplate-loader will add html to the Angular global module's $templateCache using the `put` method in the module run stage, so these templates will always be available via the `$get` method, which is what is used internally whenever you require templates in Angular using `templateUrl` or ` 'app/js/directives/templateTest/templateTest.html 15 | 16 | angular.module('templateTest', []).directive('templateTest', function() { 17 | return { 18 | templateUrl: template 19 | } 20 | }); 21 | ``` 22 | 23 | Notice how we get a url back when we import the html file, instead of a string of html? This url serves as the "key" for `$templateCache.$get`. In this case, `relativeTo=/app/` would give us a return value of `js/directives/templateTest/templateTest.html` above. -------------------------------------------------------------------------------- /content/Authoring-libraries.md: -------------------------------------------------------------------------------- 1 | Webpack can be handy for packaging your library for general consumption. You can use it to output UMD, a format that's compatible with various module loaders (CommonJS, AMD) and globals. 2 | 3 | ## How can I output UMD for my library? 4 | 5 | Especially if you are creating a library, it can be useful to output an UMD version of your library. This can be achieved using the following snippet: 6 | 7 | ```javascript 8 | output: { 9 | path: './dist', 10 | filename: 'mylibrary.js', 11 | libraryTarget: 'umd', 12 | library: 'MyLibrary', 13 | }, 14 | ``` 15 | 16 | In order to avoid bundling big dependencies like Angular, you'll want to use a configuration like this in addition: 17 | 18 | ```javascript 19 | externals: { 20 | angular: 'angular', 21 | }, 22 | ``` 23 | 24 | ## How can I output a minified version of my library? 25 | 26 | Here's the basic idea: 27 | 28 | ```javascript 29 | output: { 30 | path: './dist', 31 | filename: 'awesomemular.min.js', 32 | libraryTarget: 'umd', 33 | library: 'Awesomemular', 34 | }, 35 | plugins: [ 36 | new webpack.optimize.UglifyJsPlugin({ 37 | compress: { 38 | warnings: false 39 | }, 40 | }), 41 | ] 42 | ``` -------------------------------------------------------------------------------- /content/Automatic-CSS-refresh.md: -------------------------------------------------------------------------------- 1 | When **webpack-dev-server** is running with [Automatic browser refresh](Automatic-browser-refresh) the CSS will also update, but a bit differently. When you do a change to a CSS file the style tag belonging to that file will be updated with the new content... without a refresh! -------------------------------------------------------------------------------- /content/Automatic-browser-refresh.md: -------------------------------------------------------------------------------- 1 | When **webpack-dev-server** is running it will watch your files for changes. When that happens it rebundles your project and notifies browsers listening to refresh. To trigger this behavior you need to change your *index.html* file in the `build/` folder. 2 | 3 | *build/index.html* 4 | ```html 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ``` 16 | 17 | We added a script that refreshes the application when a change occurs. You will also need to add an entry point to your configuration: 18 | 19 | ```javascript 20 | var path = require('path'); 21 | 22 | module.exports = { 23 | entry: ['webpack/hot/dev-server', path.resolve(__dirname, '../app/main.js')], 24 | output: { 25 | path: path.resolve(__dirname, '../build'), 26 | filename: 'bundle.js' 27 | }, 28 | }; 29 | ``` 30 | 31 | That's it! Now your application will automatically refresh on file changes. 32 | 33 | ## Default environment 34 | In the example above we created our own *index.html* file to give more freedom and control. It is also possible to run the application from **http://localhost:8080/webpack-dev-server/bundle**. This will fire up a default *index.html* file that you do not control. It also fires this file up in an iFrame allowing for a status bar to indicate the status of the rebundling process. -------------------------------------------------------------------------------- /content/Basics.md: -------------------------------------------------------------------------------- 1 | ## Why use Webpack? 2 | - Minimal configuration for the common workflow 3 | - Analyzes your project code as chunks that can be rearranged to optimize user experience 4 | - Especially useful with Angular JS 5 | - Powerful features like automatically inlining images, fonts and various types of template languages 6 | - ES6 support right out of the box 7 | - Modules with node-style dependency trees promote better code structure 8 | - A single bundle file makes deployment easy 9 | - Develop on the minified output file with source maps for no optimization surprises -------------------------------------------------------------------------------- /content/CSS,-Fonts,-Images.md: -------------------------------------------------------------------------------- 1 | Historically web frontends have been split in three separate parts: structure markup (ie. HTML), style (ie. CSS) and logic (ie. JavaScript). You end up with something functional once you have these together. A well designed site should work even without JavaScript enabled and the content should make sense even without CSS. In practice this isn't always true and depending on your requirements you may be able to flex here. 2 | 3 | I won't go into detail to explain why we ended up with HTML, CSS and JavaScript. This is the triad what you'll build will most likely be using given that's what browsers support. In case of JavaScript you might compile down to something that's ES5 compatible and set up shims where needed. Shims can come in handy if you need to patch functionality for old browsers (IE 8 and such) and can help to an extent. A site such as [caniuse.com](http://caniuse.com/) can be helpful in figuring out what works and where and whether shims exist. 4 | 5 | ## CSS 6 | 7 | In its essence CSS is aspect oriented programming. It will allow you attach some behavior to selected features. Sometimes the boundary between CSS and JavaScript can be quite fuzzy and at times people have achieved amazing feats just by using CSS. That doesn't mean it should be used that way but it's always fun to push the envelope a bit. 8 | 9 | These days CSS incorporates a powerful selector syntax, media queries (customize per resolution), animation capabilities, whatnot. Depending on what you are doing you may whether love or hate its layout capabilities. Originally CSS was designed page layout in mind. These days we live more and more in the world of apps so you might need to struggle every once in a while a bit. 10 | 11 | Understanding the box model of CSS can help a lot. In addition the usage of CSS reset can be helpful. That eliminates some of the rendering differences between various browsers and provides more consistent results. In addition settings attributes such as `box-sizing: border-box;` can make the language more intuitive to use. 12 | 13 | > Instead of CSS we could be writing something like JavaScript, namely [JSS](https://en.wikipedia.org/wiki/JavaScript_Style_Sheets), if things had gone differently. 14 | 15 | ### Missing Features 16 | 17 | The core language is missing a lot of power programmers take granted. You won't have things such as variables or functions. This is the main reason why various preprocessors have appeared. They add some new functionality that has great potential to simplify your work as a developer. It isn't very [hard to write a system of your own](http://www.nixtu.info/2011/12/how-to-write-css-preprocessor-using.html). That said these days people like to stick with something like [Sass](http://sass-lang.com/), [Less](http://lesscss.org/) or [Stylus](http://learnboost.github.io/stylus/). Each to his own. 18 | 19 | Using webpack makes it easy to utilize these solutions. Often all you need to do is to set up a loader and then just `require` the assets you need. Images, fonts and such take extra setup but that's what this chapter is about so read on. 20 | 21 | ### Conventions 22 | 23 | There isn't single right way to develop CSS. As a result many approaches have appeared. Examples: [SMACSS](https://smacss.com/), [Suit CSS](http://suitcss.github.io/), [BEM](https://bem.info/). 24 | 25 | On macro level there are entire schools of design such as [Responsive Web Design](https://en.wikipedia.org/wiki/Responsive_web_design), Mobile First Design or [Atomic Web Design](http://bradfrost.com/blog/post/atomic-web-design/). 26 | 27 | You could say moving declarations back to markup is backwards. After all, inline styles have been frowned upon a long time. Every once in a while it is a good idea to challenge the dogma and move ahead. Maybe it can be useful to have all relevant information on component level. Even if you have basic style declarations on component level this doesn't mean you couldn't inject customizations from higher level if you design your components right. 28 | 29 | Of course there are questions such are these kind of ideas compatible with CSS frameworks such as [Bootstrap](http://getbootstrap.com/), [Foundation](http://foundation.zurb.com/) or [Pure](http://purecss.io/) but that's another topic. 30 | 31 | ## Fonts 32 | 33 | The simplest way to make your page look nicer is to set 34 | 35 | ```css 36 | body { 37 | font-family: Sans-Serif; 38 | } 39 | ``` 40 | 41 | Of course CSS provides [a ton of other options](https://developer.mozilla.org/en/docs/Web/CSS/font). Even more interesting these days we have [web fonts](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face) which provide additional options for designers. 42 | 43 | ## Images 44 | 45 | A large amount of content we consume is image based. Especially thanks to the introduction of high resolution displays on mobile devices, there are additional challenges available. You could say the situation is still not ideal but we are getting there thanks to initiatives such as [Responsive Images Community Group](http://responsiveimages.org/). It will take a little bit of effort to serve the correct images for various devices but in the end if you want to provide the best service, you should go the extra mile. -------------------------------------------------------------------------------- /content/Configuring-Angular-JS.md: -------------------------------------------------------------------------------- 1 | ## Installing Angular JS 2 | 3 | `npm install angular --save` 4 | 5 | There is really nothing more to it. You can now start using Angular JS in your code. In this project we are using ES6 module loader sytax, where `import controller from './controllers.js;` is equivalent to `var controller = require('./controllers.js');`. 6 | 7 | ## Using Angular JS in the code 8 | 9 | *In any file* 10 | ```javascript 11 | import controller from './controllers.js'; 12 | 13 | angular 14 | 15 | .module('main', []) 16 | 17 | .controller('mainController', controller); 18 | ``` 19 | 20 | ## Including Angular JS as a plugin 21 | To use use Angular JS in your JavaScript modules, you can use webpack to make Angular available as a global to all modules. Otherwise, you can require Angular in any module file where you need it. 22 | 23 | `var angular = require('angular');` 24 | 25 | Here's how we configure webpack to use Angular as a plugin. 26 | 27 | *webpack.config.js* 28 | ```javascript 29 | var path = require('path'); 30 | module.exports = { 31 | entry: path.resolve(__dirname, '../app/main.js') 32 | output: { 33 | path: path.resolve(__dirname, '../build'), 34 | filename: 'bundle.js' 35 | }, 36 | plugins: [ 37 | new webpack.ProvidePlugin({ 38 | 'angular': 'angular' 39 | }) 40 | ] 41 | }; 42 | ``` 43 | 44 | Webpack will map modules to free variables. In this case, any time you use the free variable `angular` inside a module, webpack will set angular to `require('angular')`. 45 | 46 | ## Including Angular JS as an external 47 | If you are writing for a page that will already have Angular loaded, you can use webpack externals. This defines a global variable for all your modules, and assumes it will be available. You may need to include the variable as a JShint global as well to avoid hinting errors. 48 | 49 | Here's how we configure webpack to use Angular as an external. 50 | 51 | *webpack.config.js* 52 | ```javascript 53 | var path = require('path'); 54 | module.exports = { 55 | entry: path.resolve(__dirname, '../app/main.js') 56 | output: { 57 | path: path.resolve(__dirname, '../build'), 58 | filename: 'bundle.js' 59 | }, 60 | externals: { 61 | 'angular': 'angular' 62 | } 63 | }; 64 | ``` 65 | 66 | You can map other global libraries which may not be a part of your app to externals as well. Be aware this will development with webpack-dev-server, since you'll have to include script tags for those sources in your `index.html`. -------------------------------------------------------------------------------- /content/Creating-a-dev-and-production-config.md: -------------------------------------------------------------------------------- 1 | ## Split configuration into multiple tasks 2 | 3 | Webpack config can be broken up to accomplish different things per npm task. For example, to have separte tasks for normal and minified output bundles, start with an npm task for each. 4 | 5 | *package.json* 6 | ```json 7 | "scripts": { 8 | "run": "webpack --watch --hot", 9 | "build": "webpack config=webpack.min.config.js" 10 | } 11 | ``` 12 | 13 | *webpack.config.js* 14 | ```javascript 15 | var path = require('path'); 16 | module.exports = { 17 | entry: path.resolve(__dirname, 'app/main.js') 18 | output: { 19 | path: path.resolve(__dirname, 'build'), 20 | filename: 'bundle.js' 21 | }, 22 | plugins: [ 23 | new webpack.ProvidePlugin({ 24 | 'angular': 'angular' 25 | }) 26 | ] 27 | }; 28 | ``` 29 | 30 | *webpack.min.config.js* 31 | ```javascript 32 | var config = require('./webpack.config'), 33 | webpack = require('webpack'); 34 | 35 | config.output.filename = 'bundle.min.js', 36 | config.plugins.push(new webpack.optimize.UglifyJsPlugin({minimize: true})); 37 | 38 | module.exports = config; 39 | ``` -------------------------------------------------------------------------------- /content/Custom-workflow-entry.md: -------------------------------------------------------------------------------- 1 | When running your workflow from **http://localhost:8080/web-dev-server/bundle** you do not control the *index.html* file where the scripts are loaded. 2 | 3 | ## Running your own index.html file 4 | In your *package.json* you have your *dev* script. `"webpack-dev-server --devtool eval --progress --colors --content-base build/"`. The *--content-base build/* parameter tells webpack-dev-server where to load your application from. In this example that would be `build/`. 5 | 6 | ## Create the index.html file 7 | In the `build/` folder create a new *index.html* with this content. -------------------------------------------------------------------------------- /content/Deploy-to-production.md: -------------------------------------------------------------------------------- 1 | There are two things you want to do preparing for a production build. 2 | 3 | * Configure a script to run in your package.json file 4 | * Create a production config OR use node environment variables to make necessary changes in your Webpack config file 5 | 6 | ### Creating the script 7 | 8 | We have already used *package.json* to create the `npm run dev` script. Now let us set up `npm run deploy`. 9 | 10 | ```json 11 | { 12 | "name": "my-project", 13 | "version": "0.0.0", 14 | "description": "My awesome project!", 15 | "main": "app/main.js", 16 | "scripts": { 17 | "dev": "webpack-dev-server --devtool eval --progress --colors --hot --content-base build", 18 | "deploy": "NODE_ENV=production webpack -p --config webpack.production.config.js" 19 | }, 20 | "author": "", 21 | "license": "ISC", 22 | "devDependencies": { 23 | "webpack": "^1.4.13", 24 | "webpack-dev-server": "^1.6.6" 25 | }, 26 | "dependencies": {} 27 | } 28 | ``` 29 | 30 | As you can see we are just running webpack with the production argument and pointing to a different configuration file. We also use the environment variable "production" to allow our required modules to do their optimizations. Lets us create the config file now. 31 | 32 | ### Creating the production config 33 | 34 | So there really is not much difference in creating the dev and production versions of your webpack config. You basically point to a different output path and there are no workflow configurations or optimizations. What you also want to bring into this configuration is cache handling. 35 | 36 | ```javascript 37 | var path = require('path'); 38 | var config = { 39 | entry: path.resolve(__dirname, '../app/main.js') 40 | output: { 41 | path: path.resolve(__dirname, '../dist'), 42 | filename: 'bundle.js' 43 | }, 44 | module: { 45 | loaders: [{ 46 | test: /\.js$/, 47 | 48 | // There is not need to run the loader through 49 | // vendors 50 | exclude: [node_modules_dir], 51 | loader: 'babel' 52 | }] 53 | } 54 | }; 55 | 56 | module.exports = config; 57 | ``` 58 | 59 | ### Doing the deploy 60 | 61 | Run `npm run deploy` in the root of the project. Webpack will now run in production mode. It does some optimizations on its own, but also React JS will do its optimizations. Look into caching for even more production configuration. -------------------------------------------------------------------------------- /content/Getting-started.md: -------------------------------------------------------------------------------- 1 | > Before getting started you should make sure you have a recent version of Node.js and NPM installed. See [nodejs.org](http://nodejs.org/) for installation details. We'll use NPM to set up various tools. 2 | 3 | Getting started with Webpack is straightforward. I'll show you how to set up a simple project based on it. As a first step, set a directory for your project and hit `npm init` and fill in some answers. That will create a `package.json` for you. Don't worry if some fields don't look ok, you can modify those later. 4 | 5 | ## Installing Webpack 6 | 7 | Next you should get Webpack installed. We'll do a local install and save it as a project dependency. This way you can invoke the build anywhere (build server, whatnot). Run `npm i webpack --save-dev`. If you want to run the tool, hit `node_modules/.bin/webpack`. 8 | 9 | ## Directory Structure 10 | 11 | Structure your project like this: 12 | 13 | - /app 14 | - main.js 15 | - component.js 16 | - /build 17 | - bundle.js (automatically created) 18 | - index.html 19 | - /config 20 | - webpack.config.js 21 | - package.json 22 | 23 | In this case we'll create `bundle.js` using Webpack based on our `/app`. To make this possible, let's set up `webpack.config.js`. 24 | 25 | ## Creating Webpack Configuration 26 | 27 | In our case a basic configuration could look like this: 28 | 29 | *webpack.config.js* 30 | 31 | ```javascript 32 | var path = require('path'); 33 | 34 | module.exports = { 35 | entry: path.resolve(__dirname, '../app/main.js'), 36 | output: { 37 | path: path.resolve(__dirname, '../build'), 38 | filename: 'bundle.js', 39 | }, 40 | }; 41 | ``` 42 | 43 | ## Running Your First Build 44 | 45 | Now that we have basic configuration in place, we'll need something to build. Let's start with a classic `Hello World` type of app. Set up `/app` like this: 46 | 47 | *app/component.js* 48 | 49 | ```javascript 50 | 'use strict'; 51 | 52 | module.exports = function () { 53 | var element = document.createElement('h1'); 54 | 55 | element.innerHTML = 'Hello world'; 56 | 57 | return element; 58 | }; 59 | ``` 60 | 61 | *app/main.js* 62 | 63 | ```javascript 64 | 'use strict'; 65 | var component = require('./component.js'); 66 | 67 | document.body.appendChild(component()); 68 | 69 | ``` 70 | 71 | Now run `webpack` in your terminal and your application will be built. A *bundle.js* file will appear in your `/build` folder. Your *index.html* file in the `build/` folder will need to load up the application. 72 | 73 | *build/index.html* 74 | 75 | ```html 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | ``` 86 | 87 | > It would be possible to generate this file with Webpack using [html-webpack-plugin](https://www.npmjs.com/package/html-webpack-plugin). You can give it a go if you are feeling adventurous. It is mostly a matter of configuration. Generally this is the way you work with Webpack. 88 | 89 | ## Running the Application 90 | 91 | Just double-click the *index.html* file or set up a web server pointing to the `build/` folder. 92 | 93 | ## Setting Up `package.json` *scripts* 94 | 95 | It can be useful to run build, serve and such commands through `npm`. That way you don't have to worry about the technology used in the project. You just invoke the commands. This can be achieved easily by setting up a `scripts` section to `package.json`. 96 | 97 | In this case we can move the build step behind `npm run build` like this: 98 | 99 | 1. `npm i webpack --save` - If you want to install Webpack just as a development dependency, you can use `--save-dev`. This is handy if you are developing a library and don't want it to depend on the tool (bad idea!). 100 | 2. Add the following to `package.json`: 101 | 102 | ```json 103 | "scripts": { 104 | "build": "webpack --config config/webpack.config.js" 105 | } 106 | ``` 107 | 108 | To invoke a build, you can hit `npm run build` now. Keep in mind that you need to specify the webpack config path with `--config path` because the config is located in the `config` folder and not the main folder. 109 | 110 | Later on this approach will become more powerful as project complexity grows. You can hide the complexity within `scripts` while keeping the interface simple. 111 | 112 | The potential problem with this approach is that it can tie you to a Unix environment in case you use environment specific commands. If so, you may want to consider using something environment agnostic, such as [gulp-webpack](https://www.npmjs.com/package/gulp-webpack). 113 | 114 | > Note that NPM will find Webpack. npm run adds it to the PATH temporarily so our simple incantation will work. 115 | -------------------------------------------------------------------------------- /content/Go-JavaScript-next.md: -------------------------------------------------------------------------------- 1 | An alternative to using vanilla js as you would in the browser is `babel-loader`. If you have not heard of babel, you must check out [babel.io](http://babel.io). It is a JavaScript transpiler that allows you to use JavaScript functionality that has not yet been implemented in the browser. 2 | 3 | ## Installing the Babel loader 4 | 5 | `npm install babel-loader --save-dev` and in your config: 6 | 7 | *webpack.config.js* 8 | 9 | ```javascript 10 | var path = require('path'); 11 | var config = { 12 | entry: path.resolve(__dirname, '../app/main.js'), 13 | output: { 14 | path: path.resolve(__dirname, '../build'), 15 | filename: 'bundle.js' 16 | }, 17 | module: { 18 | loaders: [{ 19 | test: /\.js$/, 20 | loader: 'babel' 21 | }] 22 | } 23 | }; 24 | 25 | module.exports = config; 26 | ``` 27 | 28 | Now you can use all the functionality Babel provides. 29 | 30 | ## ES6 and Angular 31 | You may use ES6 syntax syntax in most cases without problems with Angular 1.x, and in some cases you can use ES6 types and constructors, but keep in mind Angular was not written for ES6, so for example you can define services as a `Class`, but not a factory, since they expect different constructors. This could lead to some messy code, but you can choose how you'd like to take advantage of ES6. -------------------------------------------------------------------------------- /content/Inlining-Fonts.md: -------------------------------------------------------------------------------- 1 | Fonts can be really difficult to get right. First of all we have typically 4 different formats, but only one of them will be used by the respective browser. You do not want to inline all 4 formats, as that will just bloat your CSS file and in no way be an optimization. 2 | 3 | ## Choose one format 4 | Depending on your project you might be able to get away with one font format. If you exclude Opera Mini, all browsers support the .woff and .svg format. The thing is that fonts can look a little bit different in the different formats, on the different browsers. So try out .woff and .svg and choose the one that looks the best in all browsers. 5 | 6 | There are probably other strategies here too, so please share by creating an issue or pull request. 7 | 8 | ## Doing the actual inlining 9 | You do this exactly like you do when inlining images. If you haven't already, add the webpack url-loader `npm install url-loader --save-dev`. 10 | 11 | ```javascript 12 | var path = require('path'); 13 | var config = { 14 | entry: path.resolve(__dirname, '../app/main.js') 15 | output: { 16 | path: path.resolve(__dirname, '../build'), 17 | filename: 'bundle.js' 18 | }, 19 | module: { 20 | loaders: [ 21 | { 22 | test: /\.woff$/, 23 | loader: 'url?limit=100000' 24 | }, 25 | { 26 | test: /\.woff2$/, 27 | loader: 'url?limit=100000' 28 | }, 29 | { 30 | test: /\.ttf$/, 31 | loader: 'url?limit=100000' 32 | }, 33 | { 34 | test: /\.eot$/, 35 | loader: 'file' 36 | }, 37 | { 38 | test: /\.svg$/, 39 | loader: 'url?limit=100000' 40 | } 41 | ] 42 | } 43 | }; 44 | ``` 45 | Just make sure you have a limit above the size of the fonts, or they will of course not be inlined. -------------------------------------------------------------------------------- /content/Inlining-Images.md: -------------------------------------------------------------------------------- 1 | Until HTTP/2 is here you want to avoid setting up too many HTTP requests when your application is loading. Depending on the browser you have a set number of requests that can run in parallel. If you load a lot of images in your CSS it is possible to automatically inline these images as BASE64 strings to lower the number of requests required. This can be based on the size of the image. There is a balance of size of download and number of downloads that you have to figure out for your project, and Webpack makes that balance easy to adjust. 2 | 3 | ## Installing the url-loader 4 | `npm install url-loader --save-dev` will install the loader that can convert resolved paths as BASE64 strings. As mentioned in other sections of this cookbook Webpack will resolve "url()" statements in your CSS as any other require or import statements. This means that if we test on image file extensions for this loader we can run them through it. 5 | 6 | ```javascript 7 | var path = require('path'); 8 | var config = { 9 | entry: path.resolve(__dirname, '../app/main.js') 10 | output: { 11 | path: path.resolve(__dirname, '../build'), 12 | filename: 'bundle.js' 13 | }, 14 | module: { 15 | loaders: [ 16 | { 17 | test: /\.(png|jpg)$/, 18 | loader: 'url?limit=25000' 19 | } 20 | ] 21 | } 22 | }; 23 | ``` 24 | The limit is an argument passed to the url-loader. It tells it that images that are 25KB or smaller in size will be converted to a BASE64 string and included in the CSS file where it is defined. -------------------------------------------------------------------------------- /content/Introduction-to-Angular-JS.md: -------------------------------------------------------------------------------- 1 | ## Basic Features 2 | 3 | Angular is a JavaScript framework that provides many useful tools and patterns to make it easier and faster to build complex front end application. It can be used in conjuction with a pluggable router and server-side url rewrites to drive a complete complete website, or on it's own with a small amount of code to build widgets or parts of a larger site. 4 | 5 | Straightforward resource models, two-way bindings, form flow control and validation, simple and powerful animation and customizable components are some of the nice things Angular streamlines for you. It may seem idiomatic, or slightly backwards to once again be working with lots of special inline markup, but you'll soon realize how powerful this framework is. What used to be hard to control and took many lines of code, is now trivial. 6 | 7 | ### Directives Basics 8 | 9 | One way of writing Angular applications is with a focus on directives, which are Angular's version of web components. You will design your application in smaller pieces, each of which has it own purpose. Some components contain more complexity than others, but may contain some or all of its logic, layout and basic styling. To give you an example of an Angular directive: 10 | 11 | ```html 12 | 13 | 14 | 15 | ``` 16 | 17 | You can see a couple of basic features of directives here. We have defined a couple of custom properties in form of `owner` and `task`. `owner` is something that is bound to a variable named `owner` that exists within the same scope as our directive. For `task` we provide a fixed value. This value can be accessed from the scope in the directive's `controller` or `link` function. 18 | 19 | In practice you would most likely structure this a little differently to fit your data model better. That goes a little beyond basic Angular, though. 20 | 21 | Angular has some special built-in directives, including `ng-repeat`, that can help us render a list of Todo Items: 22 | 23 | ```html 24 | 29 | ``` 30 | 31 | ### Entire Component 32 | 33 | To give you a better idea of what components look like, let's expand our `TodoItem` example into code (ES6 + html). I've done this below and will walk you through it: 34 | 35 | *component.html* 36 | ```html 37 |
38 | {{ todo.owner }} 39 | {{ todo.task }} 40 | 41 | 42 |
43 | ``` 44 | 45 | *component.js* 46 | ```javascript 47 | import angular from 'angular'; 48 | import template from './component.html'; 49 | 50 | angular 51 | .module('TodoListDirective', []) 52 | .directive('TodoList', function($scope) { 53 | return { 54 | scope: true, 55 | template: template, 56 | replace: true, 57 | controllerAs: 'todo', 58 | controller: function(attrs) { 59 | this.owner = attrs.owner; 60 | this.task = attrs.task; 61 | this.likes = 0; 62 | } 63 | } 64 | }); 65 | ``` 66 | 67 | You can see some basic features of an Angular directive above. We're be using the newer `controllerAs` syntax to keep our scope namespace tidy and componetized, and creating an isolate scope for our directive by setting `scope = true`. This keeps the $scope properties in the directive from leaking up. Then we bind the initial values of the directive to the controller object to match directive markup. For fun, another built-in Angular directive -- `ng-click` -- to the likes element to demonstrate how powerful they can be. When clicked, the expression inside is evaluated. In this case we can implement an extra feature, liking. The current implementation just increments the number of likes per component. 68 | 69 | In practice you would transmit like counts to a backend and add some validation there but this is a good starting point for understanding how state works in Angular. 70 | 71 | In this example CSS naming has been modeled after [Suit CSS](http://suitcss.github.io/) conventions as those look clean to me. That's just one way to deal with it. 72 | 73 | ### Dealing with Manipulation 74 | 75 | Let's say we want to modify the owner of our TodoItems. For the sake of simplicity let's expect it's just a string and owner is the same for all TodoItems. Based on this design it would make sense to have an input for owner at our user interface. Two way bindings in Angular make this incredibly simple. A naive implementation would look something like this: 76 | 77 | ```html 78 |
80 |
81 | 82 | ``` 83 | 84 | ### Testing 85 | 86 | If you get serious about the Todo app, Angular makes testing pretty easy. You can set up Karma for unit tests, and Angular provides a test harness called Protractor for end to end testing. 87 | -------------------------------------------------------------------------------- /content/Introduction-to-Webpack.md: -------------------------------------------------------------------------------- 1 | In web development we deal with a lot of small technical artifacts. You use HTML to describe page structure, CSS how to style it and JavaScript for logic. Or you can replace HTML with something like Jade, CSS with Sass or LESS, JavaScript with CoffeeScript, TypeScript and the ilk. In addition you have to deal with project dependencies (ie. external libraries and such). 2 | 3 | There are good reasons why we use these various technologies. Regardless of what we use, however, we still want to end up with something that can be run on the browsers of the clients. This is where build systems come in. Historically speaking there have been many. [Make](https://en.wikipedia.org/wiki/Make_%28software%29) is perhaps the most known one and still a viable option in many cases. In the world of frontend development particularly [Grunt](http://gruntjs.com/) and [Gulp](http://gulpjs.com/) have gained popularity. Both are made powerful by plugins. [NPM](https://www.npmjs.com/), the Node.js package manager, is full of those. 4 | 5 | ## Grunt 6 | 7 | Grunt is the older project. It relies on plugin specific configuration. This is fine up to a point but believe me, you don't want to end up having to maintain a 300 line `Gruntfile`. The approach simply turns against itself at some point. Just in case you are curious what the configuration looks like, here's an example from [Grunt documentation](http://gruntjs.com/sample-gruntfile): 8 | 9 | ```javascript 10 | module.exports = function(grunt) { 11 | 12 | grunt.initConfig({ 13 | jshint: { 14 | files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'], 15 | options: { 16 | globals: { 17 | jQuery: true 18 | } 19 | } 20 | }, 21 | watch: { 22 | files: ['<%= jshint.files %>'], 23 | tasks: ['jshint'] 24 | } 25 | }); 26 | 27 | grunt.loadNpmTasks('grunt-contrib-jshint'); 28 | grunt.loadNpmTasks('grunt-contrib-watch'); 29 | 30 | grunt.registerTask('default', ['jshint']); 31 | 32 | }; 33 | ``` 34 | 35 | ## Gulp 36 | 37 | Gulp takes a different approach. Instead of relying on configuration per plugin you deal with actual code. Gulp builds on top of the tried and true concept of piping. If you are familiar with Unix, it's the same here. You simply have sources, filters and sinks. In this case sources happen to match to some files, filters perform some operations on those (ie. convert to JavaScript) and then output to sinks (your build directory etc.). Here's a sample `Gulpfile` to give you a better idea of the approach taken from the project README and abbreviated a bit: 38 | 39 | ```javascript 40 | var gulp = require('gulp'); 41 | var coffee = require('gulp-coffee'); 42 | var concat = require('gulp-concat'); 43 | var uglify = require('gulp-uglify'); 44 | var sourcemaps = require('gulp-sourcemaps'); 45 | var del = require('del'); 46 | 47 | var paths = { 48 | scripts: ['client/js/**/*.coffee', '!client/external/**/*.coffee'], 49 | }; 50 | 51 | // Not all tasks need to use streams 52 | // A gulpfile is just another node program and you can use all packages available on npm 53 | gulp.task('clean', function(cb) { 54 | // You can use multiple globbing patterns as you would with `gulp.src` 55 | del(['build'], cb); 56 | }); 57 | 58 | gulp.task('scripts', ['clean'], function() { 59 | // Minify and copy all JavaScript (except vendor scripts) 60 | // with sourcemaps all the way down 61 | return gulp.src(paths.scripts) 62 | .pipe(sourcemaps.init()) 63 | .pipe(coffee()) 64 | .pipe(uglify()) 65 | .pipe(concat('all.min.js')) 66 | .pipe(sourcemaps.write()) 67 | .pipe(gulp.dest('build/js')); 68 | }); 69 | 70 | // Rerun the task when a file changes 71 | gulp.task('watch', function() { 72 | gulp.watch(paths.scripts, ['scripts']); 73 | }); 74 | 75 | // The default task (called when you run `gulp` from cli) 76 | gulp.task('default', ['watch', 'scripts']); 77 | ``` 78 | 79 | Given the configuration is code you can always just hack it if you run into troubles. You can wrap existing Node.js modules as Gulp plugins and so on. You still end up writing a lot of boilerplate for casual tasks, though. 80 | 81 | ## Browserify 82 | 83 | Dealing with JavaScript modules has always been a bit of a problem given the language actually doesn't have a concept of module till ES6. Ergo we are stuck with the 90s when it comes to browser environment. Various solutions, including [AMD](http://browserify.org/), have been proposed. In practice it can be useful just to use CommonJS, the Node.js format, and let tooling deal with the rest. The advantage is that you can often hook into NPM and avoid reinventing the wheel. 84 | 85 | [Browserify](http://browserify.org/) solves this problem. It provides a way to bundle CommonJS modules together. You can hook it up with Gulp. In addition there are tons of smaller transformation tools that allow you to move beyond the basic usage (ie. [watchify](https://www.npmjs.com/package/watchify) provides a file watcher that creates bundles for you during development automatically). This will save some effort and no doubt is a good solution up to a point. 86 | 87 | ## Webpack 88 | 89 | Webpack expands on the idea of hooking into CommonJS `require`. What if you could just `require` whatever you needed in your code, be it CoffeeScript, Sass, Markdown or something? Well, Webpack does just this. It takes your dependencies, puts them through loaders and outputs browser compatible static assets. All of this is based on configuration. Here is a sample configuration from [the official Webpack tutorial](http://webpack.github.io/docs/tutorials/getting-started/): 90 | 91 | ```javascript 92 | module.exports = { 93 | entry: './entry.js', 94 | output: { 95 | path: __dirname, 96 | filename: 'bundle.js' 97 | }, 98 | module: { 99 | loaders: [ 100 | { 101 | test: /\.css$/, 102 | loader: 'style!css' 103 | } 104 | ] 105 | } 106 | }; 107 | ``` 108 | 109 | In the following sections we'll build on top of this idea and show how powerful it is. You can, and probably should, use Webpack with some other tools. It won't solve everything. It does solve the difficult problem of bundling, however, and that's one worry less during development. -------------------------------------------------------------------------------- /content/Lazy-loaded-entry-points.md: -------------------------------------------------------------------------------- 1 | [ocLazyLoad](https://oclazyload.readme.io/docs/webpack) will give you the ability to lazily load those split entry points. -------------------------------------------------------------------------------- /content/Loading-CSS.md: -------------------------------------------------------------------------------- 1 | Webpack allows you to load CSS like you load any other code. What strategy you choose is up to you, but you can do everything from loading all your css in the main entry point file to one css file for each component. 2 | 3 | Loading CSS requires the **css-loader** and the **style-loader**. They have two different jobs. The **css-loader** will go through the CSS file and find `url()` expressions and resolve them. The **style-loader** will insert the raw css into a style tag on your page. 4 | 5 | ## Preparing CSS loading 6 | Install the two loaders: `npm install css-loader style-loader --save-dev`. 7 | 8 | In the *webpack.config.js* file you can add the following loader configuration: 9 | 10 | *webpack.config.js* 11 | ```javascript 12 | var path = require('path'); 13 | var config = { 14 | entry: path.resolve(__dirname, '../app/main.js') 15 | output: { 16 | path: path.resolve(__dirname, '../build'), 17 | filename: 'bundle.js' 18 | }, 19 | module: { 20 | loaders: [ 21 | { 22 | test: /\.css$/, // Only .css files 23 | loader: 'style!css' // Run both loaders 24 | } 25 | ] 26 | } 27 | }; 28 | 29 | module.exports = config; 30 | ``` 31 | 32 | ## Loading a CSS file 33 | Loading a CSS file is a simple as loading any file: 34 | 35 | *main.js* 36 | ```javascript 37 | import './main.css'; 38 | // Other code 39 | ``` 40 | 41 | *component.js* 42 | ```javascript 43 | import './component.css'; 44 | import myComponent from './myComponent.js'; 45 | 46 | angular 47 | .module('myDirectives', []) 48 | .directives('myComponent', myComponent); 49 | 50 | ``` 51 | 52 | **Note!** You can of course do this with both CommonJS and AMD. 53 | 54 | ## CSS loading strategies 55 | Depending on your application you might consider three main strategies. In addition to this you should consider including some of your basic CSS inlined with the initial payload (index.html). This will set the structure and maybe a loader while the rest of your application is downloading and executing. 56 | 57 | ### All in one 58 | In your main entry point, e.g. `app/main.js` you can load up your entire CSS for the whole project: 59 | 60 | *app/main.js* 61 | ```javascript 62 | import './project-styles.css'; 63 | // Other JS code 64 | ``` 65 | 66 | The CSS is included in the application bundle and does not need to download. 67 | 68 | 69 | ### Lazy loading 70 | If you take advantage of lazy loading by having multiple entry points to your application, you can include specific CSS for each of those entry points: 71 | 72 | *app/main.js* 73 | ```javascript 74 | import './style.css'; 75 | // Other JS code 76 | ``` 77 | 78 | *app/entryA/main.js* 79 | ```javascript 80 | import './style.css'; 81 | // Other JS code 82 | ``` 83 | 84 | *app/entryB/main.js* 85 | ```javascript 86 | import './style.css'; 87 | // Other JS code 88 | ``` 89 | 90 | You divide your modules by folders and include both CSS and JavaScript files in those folders. Again, the imported CSS is included in each entry bundle when running in production. 91 | 92 | ### Component specific 93 | With this strategy you create a CSS file for each component. It is common to namespace the CSS classes with the component name, thus avoiding some class of one component interfering with the class of an other. 94 | 95 | *app/components/MyComponent.css* 96 | ```css 97 | .MyComponent-wrapper { 98 | background-color: #EEE; 99 | } 100 | ``` 101 | 102 | *app/components/MyComponent.js* 103 | ```js 104 | import './MyComponent.css'; 105 | import myComponent from './myComponent.js'; 106 | 107 | angular 108 | .module('myDirectives', []) 109 | .directive('myComponent', myComponent); 110 | ``` -------------------------------------------------------------------------------- /content/Loading-HTML.md: -------------------------------------------------------------------------------- 1 | Webpack allows you to load HTML like you load any other code. This allows you to do things like require images or vector graphics in your templates, pre-process them using the templating language of your choice like jade or handlebars, and optimize the loading and reuse of templates by [template caching](Angular-Template-Cache). 2 | 3 | Loading HTML requires the **html-loader**, which will parse the HTML and return it as a function with an html string value of your template. You can use that function in places that take an html string, like `template` or in a `$compile`. 4 | 5 | ## Preparing HTML loading 6 | Install the loader: `npm install html-loader --save-dev`. 7 | 8 | In the *webpack.config.js* file you can add the following loader configuration: 9 | 10 | *webpack.config.js* 11 | ```javascript 12 | var path = require('path'); 13 | var config = { 14 | entry: path.resolve(__dirname, '../app/main.js') 15 | output: { 16 | path: path.resolve(__dirname, '../build'), 17 | filename: 'bundle.js' 18 | }, 19 | module: { 20 | loaders: [ 21 | { 22 | test: /\.html$/, // Only .html files 23 | loader: 'html' // Run html loader 24 | } 25 | ] 26 | } 27 | }; 28 | 29 | module.exports = config; 30 | ``` 31 | 32 | ## Loading an HTML file 33 | Loading an HTML file is a simple as loading any file: 34 | 35 | *main.js* 36 | ```javascript 37 | import './main.html'; 38 | // Other code 39 | ``` 40 | 41 | *component.js* 42 | ```javascript 43 | import './component.html'; 44 | import myComponent from './myComponent.js'; 45 | 46 | angular 47 | .module('myDirectives', []) 48 | .directives('myComponent', myComponent); 49 | 50 | ``` 51 | 52 | **Note!** You can of course do this with both CommonJS and AMD. -------------------------------------------------------------------------------- /content/Loading-LESS-or-SASS.md: -------------------------------------------------------------------------------- 1 | If you want to use compiled CSS, there are two loaders available for you. The **less-loader** and the **sass-loader**. Depending on your preference, this is how you set it up. 2 | 3 | ## Installing and configuring the loader 4 | `npm install less-loader` or `npm install sass-loader`. 5 | 6 | *webpack.config.js* 7 | ```javascript 8 | var path = require('path'); 9 | var config = { 10 | entry: path.resolve(__dirname, '../app/main.js') 11 | output: { 12 | path: path.resolve(__dirname, '../build'), 13 | filename: 'bundle.js' 14 | }, 15 | module: { 16 | loaders: [ 17 | // LESS 18 | { 19 | test: /\.less$/, 20 | loader: 'style!css!less' 21 | }, 22 | 23 | // SASS 24 | { 25 | test: /\.scss$/, 26 | loader: 'style!css!sass' 27 | } 28 | ] 29 | } 30 | }; 31 | ``` 32 | 33 | ## What about imports in LESS and SASS? 34 | If you import one LESS/SASS file from an other, use the exact same pattern as anywhere else. Webpack will dig into these files and figure out the dependencies. 35 | 36 | ```less 37 | @import "./variables.less"; 38 | ``` 39 | 40 | You can also load LESS files directly from your node_modules directory. Prepending the `~` resolves the dependency as a module, instead of a relative file path. 41 | ```less 42 | $import "~bootstrap/less/bootstrap"; 43 | ``` -------------------------------------------------------------------------------- /content/Matchers.md: -------------------------------------------------------------------------------- 1 | ## What kind of matchers can I use? 2 | 3 | * `{ test: /\.es6\.js$/, loader: 'babel' }` - Matches just es6.js 4 | * `{ test: /\.(es6|js)$/, loader: 'babel' }` - Matches both js and es6 5 | * It's just a JavaScript regex, so standard tricks apply. You can use a tool like [Regulex](http://jex.im/regulex/) to inspect your matchers. -------------------------------------------------------------------------------- /content/Multiple-entry-points.md: -------------------------------------------------------------------------------- 1 | Maybe you are building an application that has multiple urls. An example of this would be a solution where you have two, or more, different URLs responding with different pages. Maybe you have one user page and one admin page. They both share a lot of code, but you do not want to load all the admin stuff for normal users. That is a good scenario for using multiple entry points. A list of use cases could be: 2 | 3 | * You have an application with multiple isolated user experiences, but they share a lot of code 4 | * You have a mobile version using less components 5 | * You have a typical user/admin application where you do not want to load all the admin code for a normal user 6 | 7 | Let us create an example with a mobile experience using less components: *webpack.production.config.js* 8 | 9 | ```js 10 | var path = require('path'); 11 | var webpack = require('webpack'); 12 | var nodeModulesDir = path.resolve(__dirname, '../node_modules'); 13 | 14 | var config = { 15 | entry: { 16 | app: path.resolve(__dirname, '../app/main.js'), 17 | mobile: path.resolve(__dirname, '../app/mobile.js'), 18 | vendors: ['angular'] // And other vendors 19 | }, 20 | output: { 21 | path: path.resolve(__dirname, '../dist'), 22 | filename: '[name].js' // Notice we use a variable 23 | }, 24 | module: { 25 | loaders: [{ 26 | test: /\.js$/, 27 | exclude: [nodeModulesDir], 28 | loader: 'babel' 29 | }] 30 | }, 31 | plugins: [ 32 | new webpack.optimize.CommonsChunkPlugin('vendors', 'vendors.js') 33 | ] 34 | }; 35 | 36 | module.exports = config; 37 | ``` 38 | 39 | This configuration will create three files in the `dist/` folder. **app.js**, **mobile.js** and **vendors.js**. Most of the code in the **mobile.js** file also exists in **app.js**, but that is what we want. We will never load **app.js** and **mobile.js** on the same page. -------------------------------------------------------------------------------- /content/Optimizing-Caching.md: -------------------------------------------------------------------------------- 1 | When users hit the URL of your application they will need to download different assets. CSS, JavaScript, HTML, images and fonts. The great thing about Webpack is that you can stop thinking how you should download all these assets. You can do it through JavaScript. 2 | 3 | 4 | > OccurenceOrderPlugin 5 | 6 | 7 | ## How can I attach hashes to my production output? 8 | 9 | * Use `[hash]`. Example: `'assets/bundle.[hash].js'` 10 | 11 | The benefit of this is that this will force the client to reload the file. There is more information about `[hash]` at [the long term caching](http://webpack.github.io/docs/long-term-caching.html) section of the official documentation. 12 | 13 | > Is it possible to change the hash only if bundle changed? -------------------------------------------------------------------------------- /content/Optimizing-Development.md: -------------------------------------------------------------------------------- 1 | We talked about how you could use the minified versions of your dependencies in development to make the rebundling go as fast as possible. Let us look at a small helper you can implement to make this a bit easier to handle. 2 | 3 | *webpack.config.js* 4 | ```javascript 5 | var webpack = require('webpack'); 6 | var path = require('path'); 7 | var nodeModulesDir = path.join(__dirname, '../node_modules'); 8 | 9 | var deps = [ 10 | 'angular/angular.min.js', 11 | 'moment/min/moment.min.js', 12 | 'underscore/underscore-min.js', 13 | ]; 14 | 15 | var config = { 16 | entry: [ 17 | 'webpack/hot/dev-server', 18 | '../app/main.js' 19 | ], 20 | output: { 21 | path: path.resolve(__dirname, '../build'), 22 | filename: 'bundle.js' 23 | }, 24 | resolve: { 25 | alias: {} 26 | }, 27 | module: { 28 | noParse: [], 29 | loaders: [] 30 | } 31 | }; 32 | 33 | // Run through deps and extract the first part of the path, 34 | // as that is what you use to require the actual node modules 35 | // in your code. Then use the complete path to point to the correct 36 | // file and make sure webpack does not try to parse it 37 | deps.forEach(function (dep) { 38 | var depPath = path.resolve(node_modules_dir, dep); 39 | config.resolve.alias[dep.split(path.sep)[0]] = depPath; 40 | config.module.noParse.push(depPath); 41 | }); 42 | 43 | module.exports = config; 44 | ``` 45 | Not all modules include a minified distributed version of the lib, but most do. Especially with large libraries like Angular JS you will get a significant improvement. 46 | 47 | ## Exposing Angular to the global scope 48 | You might be using distributed versions that requires Angular JS on the global scope. To fix that you can install the expose-loader by `npm install expose-loader --save-dev` and set up the following config, focusing on the *module* property: 49 | 50 | ```javascript 51 | var webpack = require('webpack'); 52 | var path = require('path'); 53 | var nodeModulesDir = path.join(__dirname, '../node_modules'); 54 | 55 | var deps = [ 56 | 'angular/angular.min.js', 57 | 'moment/min/moment.min.js', 58 | 'underscore/underscore-min.js', 59 | ]; 60 | 61 | var config = { 62 | entry: ['webpack/hot/dev-server', '../app/main.js'], 63 | output: { 64 | path: path.resolve(__dirname, '../build'), 65 | filename: 'bundle.js' 66 | }, 67 | resolve: { 68 | alias: {} 69 | }, 70 | module: { 71 | noParse: [], 72 | 73 | // Use the expose loader to expose the minified Angular JS 74 | // distribution. 75 | loaders: [{ 76 | test: path.resolve(nodeModulesDir, deps[0]), 77 | loader: "expose?angular" 78 | }] 79 | } 80 | }; 81 | 82 | deps.forEach(function (dep) { 83 | var depPath = path.resolve(nodeModulesDir, dep); 84 | config.resolve.alias[dep.split(path.sep)[0]] = depPath; 85 | config.module.noParse.push(depPath); 86 | }); 87 | 88 | module.exports = config; 89 | ``` -------------------------------------------------------------------------------- /content/Optimizing-Rebundling.md: -------------------------------------------------------------------------------- 1 | You might notice after requiring Angular JS into your project that the time it takes from a save to a finished rebundle of your application takes more time. In development you ideally want from 200-800 ms rebundle speed, depending on what part of the application you are working on. 2 | 3 | ## Running minified file in development 4 | Instead of making Webpack go through Angular JS and all its dependencies, you can override the behavior in development. 5 | 6 | *webpack.config.js* 7 | ```javascript 8 | var path = require('path'); 9 | var nodeModules = path.resolve(__dirname, '../node_modules'); 10 | var pathToAngular = path.resolve(nodeModules, 'angular/angular.min.js'); 11 | 12 | var config = { 13 | entry: path.resolve(__dirname, '../app/main.js'), 14 | resolve: { 15 | alias: { 16 | 'angular': pathToAngular 17 | } 18 | }, 19 | output: { 20 | path: path.resolve(__dirname, '../build'), 21 | filename: 'bundle.js' 22 | }, 23 | module: { 24 | noParse: [pathToAngular] 25 | } 26 | }; 27 | 28 | module.exports = config; 29 | ``` 30 | We do two things in this configuration: 31 | 32 | 1. Whenever "angular" is required in the code it will fetch the minified Angular JS file instead of going to *node_modules* 33 | 34 | 2. Whenever Webpack tries to parse the minified file, we stop it, as it is not necessary 35 | 36 | Take a look at [Optimizing development](Optimizing-development) for more information on this. -------------------------------------------------------------------------------- /content/Optimizing-Workflow.md: -------------------------------------------------------------------------------- 1 | - Using bower files with noparse to improve rebundle speed -------------------------------------------------------------------------------- /content/README.md: -------------------------------------------------------------------------------- 1 | [Gitbook version](http://dmachat.github.io/angular-webpack-cookbook/) 2 | 3 | > In case you want to contribute, please create a PR against the [main repo](https://github.com/dmachat/angular-webpack-cookbook) or contact us through the [issue tracker](https://github.com/dmachat/angular-webpack-cookbook/issues). 4 | 5 | Inspiration for, and much of the webpack content in this cookbook comes from [christianalfoni/react-webpack-cookbook](https://github.com/christianalfoni/react-webpack-cookbook/wiki). Check it out if you want to know more about Webpack and React. 6 | 7 | The purpose of this cookbook is to guide you into the world of Angular and Webpack. Both are powerful technologies and when used together, frontend development becomes a joy. 8 | 9 | The cookbook should have something to offer for all skill levels. If you are interested in just Angular, skip the Webpack part and vice versa. 10 | 11 | ## Angular 12 | From the [AngularJS docs](https://docs.angularjs.org/guide/introduction): AngularJS is a structural framework for dynamic web apps. It lets you use HTML as your template language and lets you extend HTML's syntax to express your application's components clearly and succinctly. Angular's data binding and dependency injection eliminate much of the code you would otherwise have to write. And it all happens within the browser, making it an ideal partner with any server technology. 13 | 14 | We're particularly concerned with [Directives](https://docs.angularjs.org/guide/directive), which allow us to use Angular to build components. 15 | 16 | ## Webpack 17 | 18 | Webpack operates on a lower level. It is a module bundler. In essence it is something that you use to build your project into deliverable components (HTML, CSS, JS). The nice thing about Webpack is that once you initially configure it, it deals with the nasty details for you. This allows you to mix various technologies within your project without a headache. 19 | 20 | If you are completely new to Webpack and want to go through a good introduction, check out [Pete Hunt's guide](https://github.com/petehunt/webpack-howto). You'll find the basics there. This guide merely complements his. -------------------------------------------------------------------------------- /content/Requiring-Files.md: -------------------------------------------------------------------------------- 1 | ## Modules 2 | 3 | Webpack allows you to use different module patterns, but "under the hood" they all work the same way. All of them also works straight out of the box. 4 | 5 | #### ES6 modules 6 | 7 | ```javascript 8 | import MyModule from './MyModule.js'; 9 | ``` 10 | 11 | #### CommonJS 12 | 13 | ```javascript 14 | var MyModule = require('./MyModule.js'); 15 | ``` 16 | 17 | #### AMD 18 | 19 | ```javascript 20 | define(['./MyModule.js'], function (MyModule) { 21 | 22 | }); 23 | ``` 24 | 25 | ## Understanding Paths 26 | 27 | A module is loaded by filepath. Imagine the following tree structure: 28 | 29 | - /app 30 | - /modules 31 | - MyModule.js 32 | - main.js (entry point) 33 | - utils.js 34 | 35 | Lets open up the *main.js* file and require *app/modules/MyModule.js* in the two most common module patterns: 36 | 37 | *app/main.js* 38 | ```javascript 39 | // ES6 40 | import MyModule from './modules/MyModule.js'; 41 | 42 | // CommonJS 43 | var MyModule = require('./modules/MyModule.js'); 44 | ``` 45 | 46 | The `./` at the beginning states "relative to the file I am in now". 47 | 48 | Now let us open the *MyModule.js* file and require **app/utils**. 49 | 50 | *app/modules/MyModule.js* 51 | ```javascript 52 | // ES6 relative path 53 | import utils from './../utils.js'; 54 | 55 | // ES6 absolute path 56 | import utils from '/utils.js'; 57 | 58 | // CommonJS relative path 59 | var utils = require('./../utils.js'); 60 | 61 | // CommonJS absolute path 62 | var utils = require('/utils.js'); 63 | ``` 64 | The **relative path** is relative to the current file. The **absolute path** is relative to the entry file, which in this case is *main.js*. 65 | 66 | ### Do I have to use file extension? 67 | 68 | No, you do not have to use *.js*, but it highlights better what you are requiring. You might have some .js files, and some .html files and even images and css can be required by Webpack. It also clearly differs from required node_modules and specific files. 69 | 70 | Remember that Webpack is a module bundler! This means you can set it up to load any format you want given there is a loader for it. We'll delve into this topic later on. -------------------------------------------------------------------------------- /content/Requiring-Images-and-Fonts.md: -------------------------------------------------------------------------------- 1 | ## Inlining images and fonts 2 | 3 | When webpack parses your html and css files, it will find instances of `url()` and `src="somefile.png` and automatically insert either a base 64 encoded data url string, or a relative link to the destination file. -------------------------------------------------------------------------------- /content/Running-a-workflow.md: -------------------------------------------------------------------------------- 1 | Hitting `npm run build` all the time will get boring eventually. Fortunately we can work around that quite easily. Let's set up `webpack-dev-server`. 2 | 3 | ## Setting up `webpack-dev-server` 4 | 5 | As a first step, hit `npm i webpack-dev-server --save`. In addition we'll need to tweak `package.json` *scripts* section to include it. Here's the basic idea: 6 | 7 | *package.json* 8 | ```json 9 | { 10 | "scripts": { 11 | "build": "webpack --config config/webpack.config.js", 12 | "dev": "webpack-dev-server --devtool eval --config config/webpack.config.js --progress --colors --hot --content-base build" 13 | } 14 | } 15 | ``` 16 | 17 | When you run `npm run dev` from your terminal it will execute the command stated as a value on the **dev** property. This is what it does: 18 | 19 | 1. `webpack-dev-server` - Starts a web service on localhost:8080 20 | 2. `--devtool eval` - Creates source urls for your code. Making you able to pinpoint by filename and line number where any errors are thrown 21 | 3. `--config config/webpack.config.js` - If you followed the directory structure from Getting Started, this points webpack to the relative config file 22 | 4. `--progress` - Will show progress of bundling your application 23 | 5. `--colors` - Yay, colors in the terminal! 24 | 6. `--content-base build` - Points to the output directory configured 25 | 26 | To recap, when you run `npm run dev` this will fire up the webservice, watch for file changes and automatically rebundle your application when any file changes occur. How neat is that! 27 | 28 | Go to **http://localhost:8080** and you should see something. 29 | -------------------------------------------------------------------------------- /content/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Navigation 2 | 3 | [Introduction](Home.md) 4 | 5 | * [Webpack](Introduction-to-Webpack.md) 6 | * [Getting started](Getting-started.md) 7 | * [Running a workflow](Running-a-workflow.md) 8 | * [Automatic browser refresh](Automatic-browser-refresh.md) 9 | * [Requiring files](Requiring-Files.md) 10 | * [Angular JS](Introduction-to-Angular-JS.md) 11 | * [Configuring Angular JS](Configuring-Angular-JS.md) 12 | * [Optimizing rebundling](Optimizing-Rebundling.md) 13 | * [Loading HTML Templates](Loading-HTML.md) 14 | * [Template Cache](Angular-Template-Cache.md) 15 | * [CSS, Fonts and Images](CSS,-Fonts,-Images.md) 16 | * [Loading CSS](Loading-CSS.md) 17 | * [Automatic CSS refresh](Automatic-CSS-refresh.md) 18 | * [Loading LESS or SASS](Loading-LESS-or-SASS.md) 19 | * [Inlining images](Inlining-Images.md) 20 | * [Inlining fonts](Inlining-Fonts.md) 21 | * [Deployment strategies](Deployment-strategies.md) 22 | * [Deploy to production](Deploy-to-production.md) 23 | * [Single bundle](Single-bundle.md) 24 | * [Split app and vendors](Split-app-and-vendors.md) 25 | * [Multiple entry points](Multiple-entry-points.md) 26 | * [Lazy loaded entry points](Lazy-loaded-entry-points.md) 27 | * [Wing It Like A Pro](Wing-It-Like-a-Pro.md) 28 | * [Optimizing development](Optimizing-Development.md) 29 | * [Go JavaScript next](Go-JavaScript-next.md) 30 | * [Optimizing caching](Optimizing-Caching.md) 31 | * [Matchers](Matchers.md) 32 | * [Lazy loading entry points](Lazy-loading-entry-points.md) 33 | * [Creating a common chunk](Creating-a-common-bundle.md) 34 | * [Chunks](Understanding-chunks.md) 35 | * [Authoring libraries](Authoring-libraries.md) -------------------------------------------------------------------------------- /content/Single-bundle.md: -------------------------------------------------------------------------------- 1 | Lets have a look at the simplest setup you can create for your application. Use a single bundle when: 2 | 3 | * You have a small application 4 | * You will rarely update the application 5 | * You are not too concerned about perceived initial loading time 6 | 7 | *webpack.production.config.js* 8 | 9 | ```javascript 10 | var path = require('path'); 11 | var config = { 12 | entry: path.resolve(__dirname, '../app/main.js'), 13 | output: { 14 | path: path.resolve(__dirname, '../dist'), 15 | filename: 'bundle.js' 16 | }, 17 | module: { 18 | loaders: [{ 19 | test: /\.js$/, 20 | loader: 'babel' 21 | }] 22 | } 23 | }; 24 | 25 | module.exports = config; 26 | ``` -------------------------------------------------------------------------------- /content/Split-app-and-vendors.md: -------------------------------------------------------------------------------- 1 | When your application is depending on other libraries, especially large ones like Angular JS, you should consider splitting those dependencies into its own vendors bundle. This will allow you to do updates to your application, without requiring the users to download the vendors bundle again. Use this strategy when: 2 | 3 | * When your vendors reaches a certain percentage of your total app bundle. Like 20% and up 4 | * You will do quite a few updates to your application 5 | * You are not too concerned about perceived initial loading time, but you do have returning users and care about optimizing the experience when you do updates to the application 6 | * Users are on mobile 7 | 8 | *webpack.production.config.js* 9 | 10 | ```javascript 11 | var path = require('path'); 12 | var webpack = require('webpack'); 13 | var nodeModulesDir = path.resolve(__dirname, '../node_modules'); 14 | 15 | var config = { 16 | entry: { 17 | app: path.resolve(__dirname, '../app/main.js'), 18 | vendors: ['angular'] // And other vendors 19 | }, 20 | output: { 21 | path: path.resolve(__dirname, '../dist'), 22 | filename: 'app.js' 23 | }, 24 | module: { 25 | loaders: [{ 26 | test: /\.js$/, 27 | exclude: [nodeModulesDir], 28 | loader: 'babel' 29 | }] 30 | }, 31 | plugins: [ 32 | new webpack.optimize.CommonsChunkPlugin('vendors', 'vendors.js') 33 | ] 34 | }; 35 | 36 | module.exports = config; 37 | ``` 38 | This configuration will create two files in the `dist/` folder. **app.js** and **vendors.js**. 39 | 40 | ####Important! 41 | 42 | Remember to add both files to your HTML file, or you will get the error: `Uncaught ReferenceError: webpackJsonp is not defined`. -------------------------------------------------------------------------------- /content/Understanding-chunks.md: -------------------------------------------------------------------------------- 1 | - Explain how webpack thinks chunks and not files 2 | - What are files to load? And what does webpack create for you? And how? -------------------------------------------------------------------------------- /content/Wing-it-like-a-pro.md: -------------------------------------------------------------------------------- 1 | > TODO: intro -------------------------------------------------------------------------------- /content/Writing-Loaders.md: -------------------------------------------------------------------------------- 1 | Let's say we want a custom Markdown loader. Ie. we would like to do something like `var readme = require('../README.md');` at our JavaScript file and inject some Markdown converted as HTML there. 2 | 3 | As it happens doing something like this is quite easy with Webpack. We'll need to implement a little loader for this and hook it up with our Webpack configuration. Consider the example below. I've included syntax highlighting just for the kicks. 4 | 5 | **loaders/markdown.js** 6 | 7 | ```javascript 8 | 'use strict'; 9 | 10 | var marked = require('marked'); 11 | var highlight = require('highlight.js'); 12 | 13 | marked.setOptions({ 14 | highlight: function(code) { 15 | return highlight.highlightAuto(code).value; 16 | } 17 | }); 18 | 19 | 20 | module.exports = function(markdown) { 21 | this.cacheable(); 22 | 23 | return marked(markdown); 24 | }; 25 | ``` 26 | 27 | **Webpack configuration** 28 | 29 | ```javascript 30 | resolve: { 31 | extensions: ['.md', ...], 32 | ... 33 | }, 34 | loaders: [ 35 | { 36 | test: /\.md$/, 37 | loader: 'html!./loaders/markdown', 38 | }, 39 | ] 40 | ``` 41 | 42 | Simple as that! You can read more about [loaders at the official documentation](http://webpack.github.io/docs/loaders.html). 43 | 44 | If you want to attach some CSS for syntax highlighting by the way, you can just `require` the needed CSS like this: `require('highlight.js/styles/github.css');`. That expects `highlight.js` has been installed correctly (ie. within `node_modules`) and Webpack can find it. You should also have `css-loader` set up like this: 45 | 46 | ```javascript 47 | { 48 | test: /\.css$/, 49 | loaders: ['style', 'css'], 50 | }, 51 | ``` 52 | 53 | This expects you have `style-loader` and `css-loader` installed into your project, preferably as development dependencies (`npm i style-loader css-loader --save-dev`). -------------------------------------------------------------------------------- /content/_Footer.md: -------------------------------------------------------------------------------- 1 | The Angular Webpack Cookbook is a work in progress by @dmachat, based on the React Webpack cookbook by @bebraw and @christianalfoni -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-webpack-cookbook", 3 | "version": "0.0.0", 4 | "description": "Angular, Webpack cookbook generator", 5 | "main": "./index.js", 6 | "scripts": { 7 | "sync-wiki": "./scripts/sync-wiki.sh", 8 | "generate-gitbook": "gitbook build ./content ./gh-pages", 9 | "generate-wiki": "node ./scripts/generate-wiki.js", 10 | "deploy-gitbook": "node ./scripts/deploy-gh-pages.js", 11 | "deploy-wiki": "./scripts/deploy-wiki.sh", 12 | "generate-and-deploy": "npm run generate-gitbook && npm run generate-wiki && npm run deploy-gitbook && npm run deploy-wiki", 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/dmachat/angular-webpack-cookbook.git" 18 | }, 19 | "keywords": [ 20 | "angular", 21 | "webpack" 22 | ], 23 | "author": "", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/dmachat/angular-webpack-cookbook/issues" 27 | }, 28 | "homepage": "https://github.com/dmachat/angular-webpack-cookbook", 29 | "dependencies": { 30 | "async": "^0.9.0", 31 | "fs-extra": "^0.18.2", 32 | "gh-pages": "^0.2.0", 33 | "gitbook-cli": "^0.3.3" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /scripts/deploy-gh-pages.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ghpages = require('gh-pages'); 4 | 5 | main(); 6 | 7 | function main() { 8 | ghpages.publish('./gh-pages', console.error.bind(console)); 9 | } 10 | -------------------------------------------------------------------------------- /scripts/deploy-wiki.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # this script expects that a gh-pages build exists already 3 | # adapted from https://gist.github.com/domenic/ec8b0fc8ab45f39403dd 4 | 5 | # go to the out directory and create a *new* Git repo 6 | cd wiki 7 | 8 | # Remove possible existing git repo. We'll replace entire wiki 9 | rm -rf .git 10 | 11 | # Init new repo 12 | git init 13 | 14 | # The first and only commit to this new Git repo contains all the 15 | # files present with the commit message "Deploy to GitHub Pages". 16 | git add . 17 | git commit -m "Deploy to Wiki" 18 | 19 | # Add origin 20 | git remote add origin git@github.com:dmachat/angular-webpack-cookbook.wiki.git 21 | 22 | # Force push from the current repo's master branch to the remote 23 | # (All previous history on the master branch will be lost, since we are overwriting it.) 24 | git push --force origin master:master 25 | -------------------------------------------------------------------------------- /scripts/generate-wiki.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | 5 | var fs = require('fs-extra'); 6 | var async = require('async'); 7 | 8 | 9 | main(); 10 | 11 | function main() { 12 | var input = './content'; 13 | var output = './wiki'; 14 | 15 | fs.mkdir(output, function() { 16 | // if it dir exists already, just override content 17 | generateWiki(input, output, function(err) { 18 | if(err) { 19 | return console.error(err); 20 | } 21 | 22 | console.log('generated wiki'); 23 | }); 24 | }); 25 | } 26 | 27 | function generateWiki(input, output, cb) { 28 | async.series([ 29 | fs.copy.bind(null, 30 | input, 31 | output 32 | ), 33 | fs.copy.bind(null, 34 | path.join(input, 'README.md'), 35 | path.join(output, 'Home.md') 36 | ), 37 | generateSidebar.bind(null, { 38 | input: path.join(input, 'SUMMARY.md'), 39 | output: path.join(output, '_Sidebar.md') 40 | }), 41 | fs.remove.bind(null, path.join(output, 'README.md')), 42 | fs.remove.bind(null, path.join(output, 'SUMMARY.md')), 43 | ], cb); 44 | } 45 | 46 | function generateSidebar(config, cb) { 47 | var data = fs.readFileSync(config.input, { 48 | encoding: 'utf8' 49 | }); 50 | 51 | data = data.replace(/.md/g, ''); 52 | 53 | fs.writeFile(config.output, data, cb); 54 | } 55 | -------------------------------------------------------------------------------- /scripts/sync-wiki.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # go to the content directory and create a *new* Git repo 3 | cd content 4 | 5 | # Remove possible existing git repo. We'll replace entire wiki 6 | rm -rf .git 7 | 8 | # Init new repo 9 | git init 10 | 11 | # Add origin 12 | git remote add origin git@github.com:dmachat/angular-webpack-cookbook.wiki.git 13 | 14 | # Reset any local changes 15 | git clean -fd 16 | git reset --hard HEAD 17 | 18 | # Update from remote wiki 19 | git pull origin master:master 20 | 21 | # Finally remove the git repository to keep changes versioned in the parent repo 22 | rm -rf .git 23 | 24 | # Some tweaks to make this work with both the wiki and the gitbook output 25 | mv _Sidebar.md SUMMARY.md 26 | mv Home.md README.md 27 | sed -i 's/)/\.md)/g' SUMMARY.md 28 | --------------------------------------------------------------------------------