Skip to content
This repository was archived by the owner on Dec 3, 2023. It is now read-only.

Commit a59e9f8

Browse files
committed
Dealing with multiple modules defined in a file
1 parent f661c0f commit a59e9f8

File tree

21 files changed

+296
-157
lines changed

21 files changed

+296
-157
lines changed

lib/AngularModuleDefinition.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,24 @@ var NullDependency = require('webpack/lib/dependencies/NullDependency');
77

88
function AngularModuleDefinitionTemplate() {}
99

10+
// The definition template leaves the call to angular.module as-is, but assigns
11+
// the result to an export with the module name.
12+
//
1013
AngularModuleDefinitionTemplate.prototype = {
1114
constructor: AngularModuleDefinitionTemplate,
1215
apply: function(dep, source){
13-
var localModuleVar = dep.localModule.variableName();
14-
source.insert(0, "var "+localModuleVar+";\n");
15-
source.replace(dep.range[0], dep.range[0]-1, "("+localModuleVar+" = module.exports = ");
16+
source.replace(dep.range[0], dep.range[0]-1,
17+
"(module.exports['"+dep.name+"'] = ");
1618
source.replace(dep.range[1], dep.range[1]-1, ")");
1719
}
1820
};
1921

2022

21-
function AngularModuleDefinition(range){
23+
function AngularModuleDefinition(range, name){
2224
NullDependency.call(this);
2325
this.Class = AngularModuleDefinition;
2426
this.range = range;
27+
this.name = name;
2528
}
2629

2730
AngularModuleDefinition.prototype = Object.create(NullDependency.prototype);

lib/AngularModuleDependency.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,17 @@ var ModuleDependency = require('webpack/lib/dependencies/ModuleDependency');
88

99
function AngularModuleDependencyTemplate() {}
1010

11+
// The dependency template leaves the dependency array as the original strings
12+
// so that angular can deal with out-of-order definitions. But a
13+
// __webpack_require__ is inserted at the top to make sure the file is pulled
14+
// in.
1115
AngularModuleDependencyTemplate.prototype = {
1216
constructor: AngularModuleDependencyTemplate,
1317
apply: function(dep, source, outputOptions, requestShortener){
1418
if( !dep.range ){
1519
return;
1620
}
17-
var comment = "", content = "require(";
21+
var comment = "", content = "__webpack_require__(";
1822
if(outputOptions.pathinfo){
1923
comment += "/*! " + requestShortener.shorten(dep.request) + " */ ";
2024
}
@@ -24,8 +28,8 @@ AngularModuleDependencyTemplate.prototype = {
2428
content += "!(function webpackMissingModule() { throw new Error(" +
2529
JSON.stringify("Cannot find module \"" + dep.request + "\"") + "); }())";
2630
}
27-
content += ").name";
28-
source.replace(dep.range[0], dep.range[1]-1, content);
31+
content += ");\n";
32+
source.insert(0, content);
2933
},
3034
applyAsTemplateArgument: function(name, dep, source){
3135
if( !dep.range ){

lib/index.js

Lines changed: 141 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -7,38 +7,67 @@
77
var path = require('path');
88

99
var LocalModulesHelpers = require("webpack/lib/dependencies/LocalModulesHelpers");
10-
var LocalModuleDependency = require("webpack/lib/dependencies/LocalModuleDependency");
1110
var NullFactory = require("webpack/lib/NullFactory");
1211
var ModuleParserHelpers = require("webpack/lib/ModuleParserHelpers");
1312
var RequireHeaderDependency = require("webpack/lib/dependencies/RequireHeaderDependency");
1413

1514
var AngularModuleDependency = require('./AngularModuleDependency');
1615
var AngularModuleDefinition = require('./AngularModuleDefinition');
1716

18-
function AngularPlugin() {}
17+
function AngularPlugin() {
18+
}
1919

2020
module.exports = AngularPlugin;
2121

22+
function bindCallbackMethod(source, plugname, obj, method){
23+
source.plugin(plugname, method.bind(obj, source));
24+
}
25+
26+
function containsSlash(str){
27+
return str.indexOf("/") >= 0 || str.indexOf("\\") >= 0;
28+
}
29+
30+
// Try to resolve a file of the form /somepath/module/module
31+
// (calling it modmod for want of a better term)
32+
function resolveModModFile(resolver, request, callback){
33+
var joined = path.join(request.request, request.request);
34+
return resolver.doResolve("file", {
35+
path: request.path,
36+
request: joined,
37+
query: request.query
38+
}, callback, true);
39+
}
40+
41+
function requestIsModModFile(request){
42+
if( containsSlash(request.request) || ! request.file ){
43+
return false;
44+
}
45+
var starting = request.path.length - request.request.length;
46+
var match = request.path.lastIndexOf(request.request);
47+
return match === starting &&
48+
(request.path[starting-1] === '/' || request.path[starting-1] === '\\');
49+
}
2250

2351
AngularPlugin.prototype = {
2452
constructor: AngularPlugin,
2553

2654
// This is the entrypoint that is called when the plugin is added to a
2755
// compiler
2856
apply: function(compiler){
29-
compiler.plugin("compilation", this.addDependencyFactories.bind(this));
30-
compiler.parser.plugin('expression angular', function(){
31-
return ModuleParserHelpers.addParsedVariable(this, 'angular',
32-
"require('angular')");
33-
});
34-
compiler.parser.plugin('call angular.module',
35-
this.parseModuleCall.bind(this, compiler.parser));
36-
compiler.resolvers.normal.plugin('module-module',
37-
this.resolveModule.bind(this, compiler.resolvers.normal));
57+
bindCallbackMethod(compiler, "compilation",
58+
this, this.addDependencyFactories);
59+
bindCallbackMethod(compiler.parser, "expression angular",
60+
this, this.addAngularVariable);
61+
bindCallbackMethod(compiler.parser, "call angular.module",
62+
this, this.parseModuleCall);
63+
bindCallbackMethod(compiler.resolvers.normal, "module-module",
64+
this, this.resolveModule);
3865
},
3966

67+
// #### Plugin Callbacks
68+
4069
// This sets up the compiler with the right dependency module factories
41-
addDependencyFactories: function(compilation, params){
70+
addDependencyFactories: function(compiler, compilation, params){
4271
compilation.dependencyFactories.set(AngularModuleDependency,
4372
params.normalModuleFactory);
4473
compilation.dependencyTemplates.set(AngularModuleDependency,
@@ -49,89 +78,126 @@ AngularPlugin.prototype = {
4978
new AngularModuleDefinition.Template());
5079
},
5180

81+
// This injects the angular module wherever it's used.
82+
addAngularVariable: function(parser) {
83+
return ModuleParserHelpers.addParsedVariable(parser, 'angular', "require('angular')");
84+
},
85+
5286
// Each call to `angular.module()` is analysed here
5387
parseModuleCall: function(parser, expr){
54-
var mod, deps;
55-
ModuleParserHelpers.addParsedVariable(parser, 'angular',
56-
"require('angular')");
57-
switch(expr.arguments.length){
58-
// A single argument is the equivalent of `require()`
59-
case 1:
60-
mod = parser.evaluateExpression(expr.arguments[0]);
61-
return this._addDependencyForParameter(parser, expr, mod);
62-
// 2 arguments mean a module definition if the 2nd is an array.
63-
case 2:
64-
mod = parser.evaluateExpression(expr.arguments[0]);
65-
// TODO: should we check the module name?
66-
this._addModuleDefinition(parser, expr, mod);
67-
deps = parser.evaluateExpression(expr.arguments[1]);
68-
if( deps.items ){
69-
return deps.items.every(
70-
this._addDependencyForParameter.bind(this, parser, expr));
71-
}
72-
return this._addDependencyForParameter(parser, expr, mod);
73-
// 3 arguments must be a module definition
74-
case 3:
75-
mod = parser.evaluateExpression(expr.arguments[0]);
76-
// TODO: should we check the module name?
77-
this._addModuleDefinition(parser, expr, mod);
78-
deps = parser.evaluateExpression(expr.arguments[1]);
79-
return deps.items.every(
80-
this._addDependencyForParameter.bind(this, parser, expr));
88+
this.addAngularVariable(parser);
89+
switch(expr.arguments.length){
90+
case 1: return this._parseModuleCallSingleArgument(parser, expr);
91+
case 2: return this._parseModuleCallTwoArgument(parser, expr);
92+
case 3: return this._parseModuleCallThreeArgument(parser, expr);
8193
default:
8294
console.warn("Don't recognise angular.module() with " +
8395
expr.arguments.length + " args");
84-
}
96+
}
8597
},
8698

99+
// Additional module resolving specific to angular modules.
100+
//
101+
// We're trying to follow as many existing conventions as possible. Including:
102+
// - dots as path separators
103+
// - camelCase module names convert to dashed file names
104+
// - module, directory and file names all the same.
105+
// - files containing multiple modules (shared prefix)
106+
//
87107
resolveModule: function(resolver, request, callback){
88-
var fs = resolver.fileSystem;
89-
var i = request.request.indexOf(".");
90-
if(i < 0) {
91-
// Try resolving a file of the form module/module
92-
return resolver.doResolve("file", {
93-
path: request.path,
94-
request: path.join(request.request, request.request),
95-
query: request.query
96-
}, callback, true);
108+
if( containsSlash(request.request) ){
109+
return callback();
97110
}
98-
// Try treating `.` as a path separator
99-
var moduleName = request.request.substr(0, i);
100-
var remainingRequest = request.request.substr(i+1);
101-
var modulePath = resolver.join(request.path, moduleName);
102-
fs.stat(modulePath, function(err, stat) {
103-
if(err || !stat) {
104-
if(callback.log){
105-
callback.log(modulePath + " doesn't exist (ng module as directory)");
111+
var split = request.request.split('.');
112+
if( split.length === 1 ) {
113+
if( ! requestIsModModFile(request) ){
114+
return resolveModModFile(resolver, request, callback);
115+
}
116+
}else{
117+
// Try treating `.` as a path separator, but in a non-greedy way.
118+
// There are lots of options here because we allow the file name to be
119+
// just a prefix.
120+
// prefer the segments to be directories and prefer the full name to be
121+
// used.
122+
var pairs = [];
123+
for( var j = 0; j > (0-split.length); j-- ){
124+
var cropped = split.slice(0, j || undefined);
125+
for( var i = 0; i < cropped.length; i++ ){
126+
pairs.push({ front: cropped.slice(0, i), back: cropped.slice(i) });
106127
}
107-
return callback();
108128
}
109-
if(stat.isDirectory()) {
110-
var types = request.directory ? "directory" : ["file", "directory"];
111-
return resolver.doResolve(types, {
112-
path: modulePath,
113-
request: remainingRequest,
129+
pairs.shift();
130+
var requests = pairs.map(function(p){
131+
return {
132+
path: path.join.apply(path, [request.path].concat(p.front)),
133+
request: p.back.join('.'),
114134
query: request.query
115-
}, callback, true);
116-
}
117-
if(callback.log){
118-
callback.log(modulePath + " is not a directory (ng module as directory)");
119-
}
120-
return callback();
121-
});
135+
};
136+
});
137+
138+
return resolver.forEachBail(requests, function(req, cb){
139+
return resolver.doResolve(["file", "directory"], req, cb, true);
140+
}, function(err, resolved){
141+
if( err || !resolved ){
142+
return callback(err);
143+
}
144+
if( resolved.file ){
145+
// We're taking this as resolved. It should contain other modules with
146+
// this prefix
147+
return resolver.doResolve("result", resolved, callback);
148+
}
149+
if(callback.log){
150+
callback.log(" is not an angular module");
151+
}
152+
callback();
153+
});
154+
}
155+
return callback();
122156
},
123157

158+
// #### Private Methods
159+
160+
// A single argument is the equivalent of `require()`
161+
// angular.module(name)
162+
_parseModuleCallSingleArgument: function(parser, expr) {
163+
var mod = parser.evaluateExpression(expr.arguments[0]);
164+
return this._addDependency(parser, expr, mod);
165+
},
166+
167+
// 2 arguments mean a module definition if the 2nd is an array.
168+
// angular.module(name, requires)
169+
// angular.module(name, configFn)
170+
_parseModuleCallTwoArgument: function(parser, expr) {
171+
var mod = parser.evaluateExpression(expr.arguments[0]);
172+
var deps = parser.evaluateExpression(expr.arguments[1]);
173+
this._addModuleDefinition(parser, expr, mod);
174+
if( deps.items ){
175+
return deps.items.every(
176+
this._addDependency.bind(this, parser, expr));
177+
}
178+
return this._addDependency(parser, expr, mod);
179+
},
180+
181+
// 3 arguments must be a module definition
182+
// angular.module(name, requires, configFn)
183+
_parseModuleCallThreeArgument: function(parser, expr) {
184+
var mod = parser.evaluateExpression(expr.arguments[0]);
185+
var deps = parser.evaluateExpression(expr.arguments[1]);
186+
this._addModuleDefinition(parser, expr, mod);
187+
return deps.items.every(
188+
this._addDependency.bind(this, parser, expr));
189+
},
124190

125191
_addModuleDefinition: function(parser, expr, mod){
126-
var dep = new AngularModuleDefinition(expr.range);
192+
var dep = new AngularModuleDefinition(expr.range, mod.string);
127193
dep.loc = expr.loc;
128194
dep.localModule = LocalModulesHelpers.addLocalModule(parser.state, mod.string);
129195
parser.state.current.addDependency(dep);
130196
return true;
131197
},
132198

133199
// A dependency (module) has been found
134-
_addDependencyForParameter: function(parser, expr, param){
200+
_addDependency: function(parser, expr, param){
135201
if( param.isConditional() ){
136202
// TODO: not sure what this will output
137203
parser.state.current.addDependency(
@@ -148,16 +214,12 @@ AngularPlugin.prototype = {
148214
}
149215
if( param.isString() ){
150216
var dep;
151-
var localModule = LocalModulesHelpers.getLocalModule(parser.state,
152-
param.string);
153-
if( localModule ){
154-
dep = new LocalModuleDependency(localModule, expr.range);
155-
dep.loc = expr.loc;
156-
parser.state.current.addDependency(dep);
217+
var localModule = LocalModulesHelpers.getLocalModule(parser.state, param.string);
218+
if( localModule ) {
157219
return true;
158220
}
159221
dep = new AngularModuleDependency(param.string, param.range);
160-
dep.log = expr.loc;
222+
dep.loc = param.loc;
161223
parser.state.current.addDependency(dep);
162224
return true;
163225
}

package.json

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,26 @@
2323
"webpack": "~1.0.0"
2424
},
2525
"devDependencies": {
26-
"grunt-contrib-jshint": "~0.9.0",
26+
"grunt-contrib-jshint": "0.10.0",
2727
"grunt-contrib-watch": "~0.6.0",
2828
"load-grunt-tasks": "~0.4.0",
29-
"time-grunt": "~0.2.0",
29+
"time-grunt": "0.3.1",
3030
"jshint-stylish": "~0.1.3",
31-
"webpack": "~1.0.0",
32-
"async": "~0.2.10",
31+
"webpack": "1.1.8",
32+
"async": "0.7.0",
3333
"lodash": "~2.4.1",
3434
"underscore.string": "~2.3.3",
3535
"karma": "~0.12.0",
3636
"webpack-dev-server": "~1.2.4",
37-
"karma-webpack": "~1.0.1",
37+
"karma-webpack": "1.1.0",
3838
"grunt-karma": "~0.8.2",
39-
"karma-jasmine": "~0.2.2",
39+
"karma-jasmine": "0.1.5",
4040
"karma-phantomjs-launcher": "~0.1.2",
41-
"exports-loader": "^0.6.2",
41+
"exports-loader": "0.6.2",
4242
"uglify-js": "~2.4.12"
4343
},
4444
"scripts": {
4545
"test": "grunt webpackScenario"
46-
}
46+
},
47+
"dependencies": {}
4748
}

tasks/webpack-scenario.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ module.exports = function(grunt){
8181
var actual = mockFs.files[p];
8282
actual = _.str.strRight(actual, IGNORE_BEFORE);
8383
var expected = grunt.file.read(abspath);
84+
grunt.file.write('bundle.js', actual);
8485
var actualZ = normalise(actual);
8586
var expectedZ = normalise(expected);
8687
if( expectedZ !== actualZ){
@@ -101,7 +102,7 @@ module.exports = function(grunt){
101102
grunt.log.debug("compiled "+confFile+" OK.");
102103
cb();
103104
}else{
104-
cb(new Error(errors.join('\n')));
105+
cb(new Error("scenario failed"));
105106
}
106107
}
107108
});

0 commit comments

Comments
 (0)