├── .coveralls.yml ├── .gitignore ├── .travis.yml ├── README.md ├── gulpfile.js ├── index.js ├── package.json ├── src ├── PushDependency.js ├── angularDecorator.js ├── moduleLoader.js └── string.js └── test ├── config ├── karma.conf.js └── webpack.conf.js ├── fixture ├── priority │ ├── a.js │ ├── a │ │ ├── dep.js │ │ └── factory.js │ ├── b.js │ └── b │ │ ├── dep.js │ │ └── factory.js └── simple │ ├── add.js │ ├── calculate.js │ ├── index.js │ ├── minus.js │ ├── moduleOutsideIndexingScope.js │ └── wrapper.js ├── spec_priority.js └── spec_simple.js /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | repo_token: N0y4hIdmdf9l4fIbXn3LSW4m6FxrKSq4a -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | node_modules 4 | dist 5 | test/out 6 | coverage -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.11" 4 | - "4" 5 | - "5" 6 | notifications: 7 | email: false 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ngrequire-webpack-plugin 2 | ======================== 3 | 4 | [![Build Status](https://travis-ci.org/randing89/ngrequire-webpack-plugin.svg?branch=master)](https://travis-ci.org/randing89/ngrequire-webpack-plugin) 5 | [![Coverage Status](https://coveralls.io/repos/randing89/ngrequire-webpack-plugin/badge.svg?branch=master)](https://coveralls.io/r/randing89/ngrequire-webpack-plugin?branch=master) 6 | [![Dependency Status](https://gemnasium.com/randing89/ngrequire-webpack-plugin.svg)](https://gemnasium.com/randing89/ngrequire-webpack-plugin) 7 | 8 | 9 | A webpack plugin to handle Angular module dependencies 10 | 11 | #Example 12 | With configuration of `new ngRequirePlugin([ './**/*.js' ])` 13 | 14 | ./a.js 15 | ```javascript 16 | angular.module('a', []) 17 | .factory('test', function(){ 18 | }); 19 | ``` 20 | 21 | ./b.js 22 | ```javascript 23 | angular.module('b', []) 24 | .controller('controllerB', function (test) { 25 | // Module 'a' will be automatically required. 26 | // And 'test' factory will also be the injected 27 | }) 28 | ``` 29 | 30 | #Usage 31 | 32 | For more detailed usage please see ./test 33 | ```javascript 34 | var ngRequirePlugin = require('ngrequire-webpack-plugin'); 35 | 36 | { 37 | plugins: [ 38 | new ngRequirePlugin(['file path list for your angular modules. eg: src/**/*.js']) 39 | ] 40 | } 41 | 42 | ``` 43 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var path = require('path'); 3 | var _ = require('lodash'); 4 | var webpack = require('webpack-stream'); 5 | var KarmaServer = require('karma').Server; 6 | var shell = require('gulp-shell'); 7 | var fs = require('fs-extra'); 8 | var async = require('async'); 9 | 10 | 11 | var ngRequirePlugin = require('./index'); 12 | 13 | var webpackConf = require('./test/config/webpack.conf.js'); 14 | var s = require('./src/string'); 15 | 16 | 17 | var base = path.resolve(__dirname); 18 | var testFolder = base + '/test'; 19 | var outFolder = testFolder + '/out'; 20 | var fixtureFolder = testFolder + '/fixture'; 21 | 22 | var paths = { 23 | test: [ 24 | path.resolve(__dirname, 'node_modules/angular/angular.js'), 25 | path.resolve(__dirname, 'node_modules/angular-mocks/angular-mocks.js'), 26 | path.resolve(__dirname, 'test/out/**/*.js'), 27 | path.resolve(__dirname, 'test/spec_*.js') 28 | ] 29 | }; 30 | 31 | var pluginConfig = { 32 | simple: { 33 | entry: './index.js', 34 | include: [ 35 | './*.js', 36 | '!./moduleOutsideIndexingScope.js' 37 | ] 38 | }, 39 | priority: { 40 | entry: ['./a.js', './b.js'], 41 | include: [ 42 | './**/*.js' 43 | ] 44 | } 45 | }; 46 | 47 | function pack(caseName, callback) { 48 | var fixturePath = path.join(fixtureFolder, caseName); 49 | var pathConfig = pluginConfig[caseName]; 50 | 51 | var conf = _.cloneDeep(webpackConf); 52 | conf.context = fixturePath; 53 | conf.entry = pathConfig.entry; 54 | 55 | conf.plugins.push(new ngRequirePlugin({ 56 | basePath: fixturePath, 57 | include: pathConfig.include 58 | })); 59 | 60 | gulp.src([ fixturePath + '/**/*.js' ]) 61 | .pipe(webpack(conf)) 62 | .pipe(gulp.dest('./test/out/' + caseName)) 63 | .on('end', callback); 64 | } 65 | 66 | gulp.task('test', function(done) { 67 | // Clear out folder 68 | fs.removeSync(outFolder); 69 | 70 | // Pack cases 71 | var tasks = []; 72 | 73 | _.each(fs.readdirSync(testFolder), function (fileName) { 74 | var filePath = path.join(testFolder, fileName); 75 | 76 | if (fs.lstatSync(filePath).isFile() && !fileName.startsWith('.')) { 77 | 78 | var caseName = path.basename(fileName, '.js').replace(/spec_/g, ''); 79 | tasks.push(pack.bind(null, caseName)); 80 | } 81 | }); 82 | 83 | async.parallel(tasks, function () { 84 | new KarmaServer({ 85 | files: paths.test, 86 | configFile: path.resolve(__dirname, './test/config/karma.conf.js'), 87 | action: 'run' 88 | }, done).start(); 89 | }); 90 | }); 91 | 92 | gulp.task('release', function () { 93 | // copy file 94 | gulp.src('') 95 | .pipe(shell(['npm version patch'])) 96 | .pipe(shell(['npm publish'])); 97 | }); 98 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var ModuleParserHelpers = require('webpack/lib/ModuleParserHelpers'); 2 | var NullFactory = require('webpack/lib/NullFactory'); 3 | var PushDependency = require('./src/PushDependency'); 4 | var ngrequire = require('ngrequire'); 5 | var _ = require('lodash'); 6 | var s = require('./src/string'); 7 | var path = require('path'); 8 | 9 | 10 | function apply(options, compiler) { 11 | // Clean cache 12 | ngrequire.clean(); 13 | 14 | compiler.plugin('compilation', function(compilation, params) { 15 | compilation.dependencyFactories.set(PushDependency, new NullFactory()); 16 | compilation.dependencyTemplates.set(PushDependency, new PushDependency.Template()); 17 | }); 18 | 19 | compiler.parser.plugin('call angular.module', function (expression) { 20 | var self = this; 21 | var filePath = self.state.current.resource || self.state.module.resource; 22 | var requireStatement = "require('{0}')"; 23 | var angularDecoratorStatement = s.escapeSlashes(requireStatement.f(path.resolve(__dirname, './src/angularDecorator'))); 24 | var moduleLoaderStatement = s.escapeSlashes(requireStatement.f(path.resolve(__dirname, './src/moduleLoader'))); 25 | 26 | ModuleParserHelpers.addParsedVariable(compiler.parser, 'angular', angularDecoratorStatement); 27 | 28 | // Update cache 29 | ngrequire.update(options.include, options.options); 30 | 31 | // Add dependencies 32 | var meta = ngrequire.getMeta(filePath); 33 | 34 | if (meta) { 35 | ModuleParserHelpers.addParsedVariable(compiler.parser, '__ngrequire_load__', moduleLoaderStatement); 36 | var deps = ngrequire.getMissingDependencies(filePath); 37 | var currentModule = meta.moduleName; 38 | var requiredModules = _.uniq(_.map(deps, 'moduleName')); 39 | var relativePaths = _.uniq(_.map(deps, 'relativePath')); 40 | 41 | _.each(relativePaths, function (relativePath, index) { 42 | var normalizedName = '__ngrequire_module_{0}__'.f(index); 43 | ModuleParserHelpers.addParsedVariable(compiler.parser, normalizedName, requireStatement.f(relativePath)); 44 | }); 45 | 46 | self.state.current.addDependency(new PushDependency(currentModule, requiredModules, filePath, expression)); 47 | } 48 | }); 49 | } 50 | 51 | module.exports = function(options) { 52 | if (options instanceof Array) { 53 | options = { 54 | include: options 55 | }; 56 | } 57 | 58 | if (!Array.isArray(options.include)) { 59 | options.include = [ options.include ]; 60 | } 61 | 62 | // Change all path to absolute 63 | var cwd = options.basePath || process.cwd(); 64 | options.include = options.include.map(function (include) { 65 | return path.resolve(cwd, include); 66 | }); 67 | 68 | return { 69 | apply: apply.bind(this, options) 70 | }; 71 | }; 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngrequire-webpack-plugin", 3 | "version": "2.0.25", 4 | "description": "A webpack plugin to automatic require AngularJS modules", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/randing89/ngrequire-webpack-plugin" 9 | }, 10 | "scripts": { 11 | "test": "./node_modules/gulp/bin/gulp.js test" 12 | }, 13 | "author": "Ran Ding", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "angular": "^1.3.11", 17 | "angular-mocks": "^1.3.0", 18 | "async": "^2.0.0-rc.5", 19 | "chai": "^3.5.0", 20 | "exports-loader": "^0.6.2", 21 | "fs-extra": "^0.30.0", 22 | "gulp": "^3.9.0", 23 | "gulp-shell": "^0.5.2", 24 | "istanbul-instrumenter-loader": "^0.2.0", 25 | "jsdom": "^9.2.1", 26 | "karma": "^0.13.22", 27 | "karma-chai": "^0.1.0", 28 | "karma-coverage": "^1.0.0", 29 | "karma-coveralls": "^1.1.2", 30 | "karma-mocha": "^1.0.1", 31 | "karma-phantomjs2-launcher": "^0.5.0", 32 | "karma-webpack": "^1.3.1", 33 | "mocha": "^2.0.1", 34 | "sigmund": "^1.0.0", 35 | "webpack": "^1.13.1", 36 | "webpack-dev-server": "^1.14.1", 37 | "webpack-stream": "^3.2.0" 38 | }, 39 | "dependencies": { 40 | "lodash": "^4.13.1", 41 | "ngrequire": "^2.0.30" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/PushDependency.js: -------------------------------------------------------------------------------- 1 | var NullDependency = require('webpack/lib/dependencies/NullDependency'); 2 | 3 | function PushDependency(currentModule, requiredModules, filePath, expression) { 4 | NullDependency.call(this); 5 | this.constructor = PushDependency; 6 | this.expression = expression; 7 | this.range = expression.range; 8 | 9 | this.filePath = filePath; 10 | this.currentModule = currentModule; 11 | this.requiredModules = requiredModules; 12 | } 13 | 14 | module.exports = PushDependency; 15 | 16 | PushDependency.prototype = Object.create(NullDependency.prototype); 17 | 18 | PushDependency.Template = function PushDependencyTemplate() {}; 19 | 20 | PushDependency.Template.prototype.apply = function(dep, source, outputOptions, requestShortener) { 21 | var requiredModules = dep.requiredModules; 22 | 23 | if (requiredModules.length === 0) { 24 | // Do not proceed if nothing to require 25 | return; 26 | } 27 | 28 | var moduleNames = []; 29 | requiredModules.forEach(function (requiredModuleName) { 30 | moduleNames.push(requiredModuleName); 31 | }); 32 | 33 | var arguments = dep.expression.arguments; 34 | // We don't want lose existing dependencies 35 | if (arguments.length > 1 && arguments[1].type === 'ArrayExpression') { 36 | // Put existing drpendencies to moduleNames list 37 | arguments[1].elements.forEach(function (element) { 38 | if (moduleNames.indexOf(element.value) === -1) { 39 | moduleNames.push(element.value); 40 | } 41 | }); 42 | } 43 | 44 | // Add quotes 45 | moduleNames = moduleNames.map(function (moduleName) { 46 | return "'{0}'".f(moduleName); 47 | }); 48 | 49 | var sourceHeader = "__ngrequire_load__('{0}', {1});\n\n".f(dep.currentModule, moduleNames.join(', ')); 50 | source.replace(dep.range[0], dep.range[1]-1, sourceHeader + "angular.module('{0}', [])".f(dep.currentModule)); 51 | }; 52 | -------------------------------------------------------------------------------- /src/angularDecorator.js: -------------------------------------------------------------------------------- 1 | var ngRegisteredModules = []; 2 | 3 | module.exports = (function () { 4 | var angular = window.angular; 5 | 6 | if (!angular) { 7 | throw new Error('window.angular is not defined'); 8 | } 9 | 10 | var origMethod = angular.module; 11 | angular.module = function(name, reqs, configFn) { 12 | reqs = reqs || []; 13 | var module = null; 14 | 15 | if (ngRegisteredModules[name]) { 16 | module = origMethod(name); 17 | module.requires.push.apply(module.requires, reqs); 18 | } else { 19 | module = origMethod(name, reqs, configFn); 20 | ngRegisteredModules[name] = module; 21 | } 22 | 23 | return module; 24 | }; 25 | 26 | return angular; 27 | })(); -------------------------------------------------------------------------------- /src/moduleLoader.js: -------------------------------------------------------------------------------- 1 | module.exports = function (moduleName) { 2 | var angular = window.angular; 3 | 4 | if (!angular) { 5 | throw new Error('window.angular is not defined'); 6 | } 7 | 8 | var providers = Array.prototype.slice.call(arguments, 1); 9 | var angularModule = angular.module; 10 | 11 | var requires = angularModule(moduleName).requires || []; 12 | 13 | for (var i = 0; i < providers.length; i++) { 14 | var provider = providers[i]; 15 | if (requires.indexOf(provider) === -1) { 16 | requires.push(provider); 17 | } 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/string.js: -------------------------------------------------------------------------------- 1 | var dollarRegexp = /\$/gi; 2 | 3 | /** 4 | * Safe replace 5 | * 6 | * @param suffix 7 | * @returns {boolean} 8 | */ 9 | if (typeof String.prototype.safeReplace !== 'function') { 10 | String.prototype.safeReplace = function(exp, replacement) { 11 | return this.replace(exp, replacement.replace(dollarRegexp, '$$$$')); 12 | }; 13 | } 14 | 15 | /** 16 | * Format a string 17 | * 18 | * @returns {String} 19 | */ 20 | if (typeof String.prototype.format !== 'function') { 21 | String.prototype.format = function () { 22 | var formatted = this; 23 | for (var i = 0; i < arguments.length; i++) { 24 | var regexp = new RegExp('\\{' + i + '\\}', 'gi'); 25 | // All $ in string will be replaced with $$ 26 | var value = (arguments[i] === undefined) ? 27 | 'undefined': 28 | arguments[i].toString(); 29 | formatted = formatted.safeReplace(regexp, value); 30 | } 31 | 32 | return formatted; 33 | }; 34 | } 35 | 36 | if (typeof String.prototype.f !== 'function') { 37 | String.prototype.f = String.prototype.format; 38 | } 39 | 40 | /** 41 | * Test if string start with prefix 42 | * 43 | * @param prefix 44 | * @returns {boolean} 45 | */ 46 | if (typeof String.prototype.startsWith !== 'function') { 47 | String.prototype.startsWith = function(suffix) { 48 | return this.indexOf(suffix) === 0; 49 | }; 50 | } 51 | 52 | /** 53 | * Test if string end with suffix 54 | * 55 | * @param suffix 56 | * @returns {boolean} 57 | */ 58 | if (typeof String.prototype.endsWith !== 'function') { 59 | String.prototype.endsWith = function(suffix) { 60 | return this.indexOf(suffix, this.length - suffix.length) !== -1; 61 | }; 62 | } 63 | 64 | module.exports = { 65 | nullOrEmpty: function (s) { 66 | return (Object.prototype.toString.call(s) === '[object String]') ? 67 | (s.trim() === '') : 68 | (s === undefined || s === null); 69 | }, 70 | 71 | escapeSlashes: function (s) { 72 | return s.replace(/\\/g, '\\\\'); 73 | } 74 | }; 75 | -------------------------------------------------------------------------------- /test/config/karma.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var rootPath = path.resolve(__dirname, '..'); 4 | 5 | module.exports = function (config) { 6 | config.set({ 7 | frameworks: ['mocha', 'chai'], 8 | reporters: ['progress', 'coverage', 'coveralls'], 9 | coverageReporter: { 10 | type: 'lcov', 11 | dir: path.join(rootPath, 'coverage') 12 | }, 13 | port: 9876, 14 | colors: true, 15 | logLevel: config.LOG_ERROR, 16 | autoWatch: true, 17 | browsers: ['PhantomJS2'], 18 | singleRun: true 19 | }); 20 | }; -------------------------------------------------------------------------------- /test/config/webpack.conf.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | output: { 4 | filename: 'bundle.js' 5 | }, 6 | resolve: { 7 | alias: { 8 | angular: './angular.js' 9 | } 10 | }, 11 | plugins: [], 12 | module: { 13 | postLoaders: [ 14 | { 15 | test: /\.js$/, 16 | exclude: /(test|node_modules|bower_components)\//, 17 | loader: 'istanbul-instrumenter' 18 | } 19 | ] 20 | } 21 | }; -------------------------------------------------------------------------------- /test/fixture/priority/a.js: -------------------------------------------------------------------------------- 1 | // Test priority 2 | // FactoryA should only include dependency from folder a 3 | angular.module('programA', []) 4 | .factory('exampleA', function (factoryA) { 5 | return factoryA(); 6 | }); -------------------------------------------------------------------------------- /test/fixture/priority/a/dep.js: -------------------------------------------------------------------------------- 1 | angular.module('priorityA') 2 | .factory('factory', function() { 3 | return function () { 4 | return 'factory dep from a'; 5 | } 6 | }); 7 | -------------------------------------------------------------------------------- /test/fixture/priority/a/factory.js: -------------------------------------------------------------------------------- 1 | angular.module('priorityA') 2 | .factory('factoryA', function(factory) { 3 | return function () { 4 | return 'factory from a and ' + factory(); 5 | } 6 | }); 7 | -------------------------------------------------------------------------------- /test/fixture/priority/b.js: -------------------------------------------------------------------------------- 1 | // Test priority 2 | // FactoryB should only include dependency from folder b 3 | angular.module('programB', []) 4 | .factory('exampleB', function (factoryB) { 5 | return factoryB(); 6 | }); -------------------------------------------------------------------------------- /test/fixture/priority/b/dep.js: -------------------------------------------------------------------------------- 1 | angular.module('priorityB') 2 | .factory('factory', function() { 3 | return function () { 4 | return 'factory dep from b'; 5 | } 6 | }); 7 | -------------------------------------------------------------------------------- /test/fixture/priority/b/factory.js: -------------------------------------------------------------------------------- 1 | angular.module('priorityB') 2 | .factory('factoryB', function(factory) { 3 | return function () { 4 | return 'factory from b and ' + factory(); 5 | } 6 | }); 7 | -------------------------------------------------------------------------------- /test/fixture/simple/add.js: -------------------------------------------------------------------------------- 1 | angular.module('math') 2 | .factory('add', function() { 3 | return function () { 4 | var sum = 0, i = 0, args = arguments, l = args.length; 5 | while (i < l) { 6 | sum += args[i++]; 7 | } 8 | return sum; 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /test/fixture/simple/calculate.js: -------------------------------------------------------------------------------- 1 | // Shortcut way 2 | angular.module('cal') 3 | .factory('calculate', function (add, minus) { 4 | return function (val) { 5 | return minus(add(val, 10), 2); 6 | } 7 | }); -------------------------------------------------------------------------------- /test/fixture/simple/index.js: -------------------------------------------------------------------------------- 1 | require('./moduleOutsideIndexingScope'); 2 | 3 | // Test standard way of specifying dependency 4 | angular.module('program', ['wrapper', 'aThirdParty']) 5 | .factory('example', function (calculate, wrap, aaa) { 6 | var a = 1; 7 | return wrap(calculate(a)) + aaa; 8 | }); -------------------------------------------------------------------------------- /test/fixture/simple/minus.js: -------------------------------------------------------------------------------- 1 | angular.module('math') 2 | .factory('minus', function() { 3 | return function () { 4 | var sum = arguments[0], i = 1, args = arguments, l = args.length; 5 | while (i < l) { 6 | sum -= args[i++]; 7 | } 8 | return sum; 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /test/fixture/simple/moduleOutsideIndexingScope.js: -------------------------------------------------------------------------------- 1 | angular.module('aThirdParty') 2 | .factory('aaa', function() { 3 | return 'aaa'; 4 | }); 5 | -------------------------------------------------------------------------------- /test/fixture/simple/wrapper.js: -------------------------------------------------------------------------------- 1 | angular.module('wrapper') 2 | .factory('wrap', function() { 3 | return function (s) { 4 | return '#' + s + '#'; 5 | } 6 | }); 7 | -------------------------------------------------------------------------------- /test/spec_priority.js: -------------------------------------------------------------------------------- 1 | describe('ngRequire Webpack Plugin', function () { 2 | it('should use module that has shortest relative path', function () { 3 | angular.mock.module('programA'); 4 | angular.mock.inject(function (exampleA) { 5 | expect(exampleA).to.be.equal('factory from a and factory dep from a'); 6 | }) 7 | }); 8 | 9 | it('should use module that has shortest relative path', function () { 10 | angular.mock.module('programB'); 11 | angular.mock.inject(function (exampleB) { 12 | expect(exampleB).to.be.equal('factory from b and factory dep from b'); 13 | }) 14 | }); 15 | }); -------------------------------------------------------------------------------- /test/spec_simple.js: -------------------------------------------------------------------------------- 1 | describe('ngRequire Webpack Plugin', function () { 2 | it('should run', function () { 3 | angular.mock.module('program'); 4 | angular.mock.inject(function (example) { 5 | expect(example).to.be.equals('#9#aaa'); 6 | }) 7 | }); 8 | }); --------------------------------------------------------------------------------