What is the best practice for importing angularjs using webpack?

How do you use Webpack and AngularJS together, and how about template loading and on demand fetching of resources?

An example of a well written webpack.config.js file for this purpose would be very much appreciated.

All code snippets displayed here can be accessed at this github repo. Code has been generously adapted from this packetloop git repo.

webpack.config.json

var path = require('path');
var ResolverPlugin = require("webpack/lib/ResolverPlugin");

var config = {
  context: __dirname,
  entry: ['webpack/hot/dev-server', './app/app.js'],
  output: {
    path: './build',
    filename: 'bundle.js'
  },
  module: {
    loaders: [{
      test: /\.css$/,
      loader: "style!css-loader"
    }, {
      test: /\.scss$/,
      loader: "style!css!sass?outputStyle=expanded"
    }, {
      test: /\.jpe?g$|\.gif$|\.png$|\.svg$|\.woff$|\.ttf$/,
      loader: "file"
    }, {
      test: /\.html$/,
      loader: "ngtemplate?relativeTo=" + path.join(__dirname, 'app/') + "!raw"
    }]
  },
  // Let webpack know where the module folders are for bower and node_modules
  // This lets you write things like - require('bower/<plugin>') anywhere in your code base
  resolve: {
    modulesDirectories: ['node_modules', 'lib/bower_components'],
    alias: {
      'npm': __dirname + '/node_modules',
      'vendor': __dirname + '/app/vendor/',
      'bower': __dirname + '/lib/bower_components'
    }
  },
  plugins: [
    // This is to help webpack know that it has to load the js file in bower.json#main
    new ResolverPlugin(
      new ResolverPlugin.DirectoryDescriptionFilePlugin("bower.json", ["main"])
    )
  ]
};

module.exports = config;

To import AngularJS into the main app.js you do the following:

app/vendor/angular.js

'use strict';

if (!global.window.angular) {
  require('bower/angular/angular');
}
var angular = global.window.angular;
module.exports = angular;

And then use it in app.js like so,

app.js

...
var angular = require('vendor/angular');

// Declare app level module
var app = angular.module('myApp', []);

...

Is the following correct? Is there an easier way to do this? I've seen a few (not a lot by any standards) posts which listed another method.

From this reddit post comment

// Add to webpack.config.js#module#loaders array
    {
      test: /[\/]angular\.js$/,
      loader: "exports?angular"
    }

There is also another plugin which is in development right now, at stackfull/angular-seed. It seems to be in the right direction, but is really really hard to use right now.

Webpack is way awesome, but the lack of documentation and samples are killing it.


Solution 1:

You can just require angular in all modules (files) where you need it. I have a github repository with example how to do that (also using webpack for build). In the example ES6 import syntax is used but it shouldnt matter, you can use standard require() instead.

Example:

import 'bootstrap/dist/css/bootstrap.min.css';
import './app.css';

import bootstrap from 'bootstrap';

import angular from 'angular';
import uirouter from 'angular-ui-router';

import { routing} from './app.config';

import common from './common/common.module';

import featureA from './feature-a/feature-a.module';
import featureB from './feature-b/feature-b.module';

const app = angular
    .module('app', [uirouter, common, featureA, featureB])
    .config(routing);

Solution 2:

I am starting with Angular + Flux with Webpack so may be I can help you with some things.

Basically I am installing everything with NPM, it has module export system, so it works like nothing. (You can use export-loader, but why if you do not need to.)

My webpack.config.js looks like this:

var webpack           = require('webpack');
var path              = require('path');
var HtmlWebpackPlugin = require("html-webpack-plugin");

var nodeModulesDir = path.resolve(__dirname, './node_modules');

// Some of my dependencies that I want
// to skip from building in DEV environment
var deps = [
  'angular/angular.min.js',
  ...
];

var config = {
  context: path.resolve(__dirname, './app'),

  entry: ['webpack/hot/dev-server', './main.js'],

  resolve: {
    alias: {}
  },

  output: {
    path: path.resolve(__dirname, './build'),
    filename: 'bundle.js'
  },

  // This one I am using to define test dependencies
  // directly in the modules
  plugins: [
    new webpack.DefinePlugin({
      ON_TEST: process.env.NODE_ENV === 'test'
    })
  ],

  module: {
    preLoaders: [
      {test: /\.coffee$/, loader: "coffeelint", exclude: [nodeModulesDir]}
    ],
    loaders: [
      {test: /\.js$/, loader: 'ng-annotate', exclude: [nodeModulesDir]},
      {test: /\.coffee$/, loader: 'coffee', exclude: [nodeModulesDir]},
        ...
    ],
    noParse: []
  },

  devtool: 'source-map'
};

if (process.env.NODE_ENV === 'production') {
  config.entry = {
    app: path.resolve(__dirname, './app/main.js'),
     vendors: ['angular']
  };
  // config.output.path = path.resolve(__dirname, './dist');
 config.output = {
   path: path.resolve(__dirname, "./dist"),
  filename: "app.[hash].js",
  hash: true
 };
 config.plugins.push(new webpack.optimize.UglifyJsPlugin());
 config.plugins.push(new webpack.optimize.CommonsChunkPlugin('vendors', 'vendors.[hash].js'));
 config.plugins.push(new HtmlWebpackPlugin({
   title: 'myApp',
   template: path.resolve(__dirname, './app/index.html'),
   inject: 'body'
 }));

 delete config.devtool;


}
  else {
    deps.forEach(function (dep) {
      var depPath = path.resolve(nodeModulesDir, dep);
      config.resolve.alias[dep.split(path.sep)[0]] = depPath;
      config.module.noParse.push(depPath);
    });
  }
module.exports = config;

My main.js looks like this:

var angular = require('angular');

if(ON_TEST) {
  require('angular-mocks/angular-mocks');
}

require('./index.coffee');

And index.coffee containt main angular module:

ngModule = angular.module 'myApp', []

require('./directive/example.coffee')(ngModule)