Skip to content

Commit bcebd7f

Browse files
authored
Merge pull request #59 from microsoft/ben/support-js-ending
Allow relative module identifiers that end with `.js`
2 parents 634f761 + a4ce7fc commit bcebd7f

File tree

6 files changed

+176
-17
lines changed

6 files changed

+176
-17
lines changed

src/core/configuration.ts

+16-5
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ namespace AMDLoader {
114114
};
115115

116116
export interface IConfigurationOptions {
117+
/**
118+
* Allow module ids to end with .js
119+
*/
120+
allowJsExtension?: boolean;
117121
/**
118122
* The prefix that will be aplied to all modules when they are resolved to a location
119123
*/
@@ -193,6 +197,7 @@ namespace AMDLoader {
193197
}
194198

195199
export interface IValidatedConfigurationOptions extends IConfigurationOptions {
200+
allowJsExtension: boolean;
196201
baseUrl: string;
197202
paths: { [path: string]: any; };
198203
config: { [moduleId: string]: IModuleConfiguration };
@@ -232,6 +237,9 @@ namespace AMDLoader {
232237
}
233238

234239
options = options || {};
240+
if (typeof options.allowJsExtension !== 'boolean') {
241+
options.allowJsExtension = false;
242+
}
235243
if (typeof options.baseUrl !== 'string') {
236244
options.baseUrl = '';
237245
}
@@ -428,12 +436,12 @@ namespace AMDLoader {
428436
/**
429437
* Transform a module id to a location. Appends .js to module ids
430438
*/
431-
public moduleIdToPaths(moduleId: string): string[] {
439+
public moduleIdToPaths(moduleIdOrPath: string): string[] {
432440

433441
if (this._env.isNode) {
434442
const isNodeModule = (
435443
this.options.amdModulesPattern instanceof RegExp
436-
&& !this.options.amdModulesPattern.test(moduleId)
444+
&& !this.options.amdModulesPattern.test(moduleIdOrPath)
437445
);
438446

439447
if (isNodeModule) {
@@ -443,15 +451,18 @@ namespace AMDLoader {
443451
return ['empty:'];
444452
} else {
445453
// ...and at runtime we create a `shortcut`-path
446-
return ['node|' + moduleId];
454+
return ['node|' + moduleIdOrPath];
447455
}
448456
}
449457
}
450458

451-
let result = moduleId;
459+
const isAbsolutePath = Utilities.isAbsolutePath(moduleIdOrPath);
460+
const isJSFilePath = (this.options.allowJsExtension ? false : Utilities.endsWith(moduleIdOrPath, '.js'));
461+
const isModuleId = !(isAbsolutePath || isJSFilePath);
452462

463+
let result = moduleIdOrPath;
453464
let results: string[];
454-
if (!Utilities.endsWith(result, '.js') && !Utilities.isAbsolutePath(result)) {
465+
if (isModuleId) {
455466
results = this._applyPaths(result);
456467

457468
for (let i = 0, len = results.length; i < len; i++) {

src/core/moduleManager.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ namespace AMDLoader {
325325
private readonly _scriptLoader: IScriptLoader;
326326
private readonly _loaderAvailableTimestamp: number;
327327
private readonly _defineFunc: IDefineFunc;
328-
private readonly _requireFunc: IRequireFunc;
328+
private readonly _requireFunc: IRequireFunc | null;
329329

330330
private _moduleIdProvider: ModuleIdProvider;
331331
private _config: Configuration;
@@ -367,7 +367,7 @@ namespace AMDLoader {
367367
private _buildInfoDefineStack: (string | null)[];
368368
private _buildInfoDependencies: string[][];
369369

370-
constructor(env: Environment, scriptLoader: IScriptLoader, defineFunc: IDefineFunc, requireFunc: IRequireFunc, loaderAvailableTimestamp: number = 0) {
370+
constructor(env: Environment, scriptLoader: IScriptLoader, defineFunc: IDefineFunc, requireFunc: IRequireFunc | null, loaderAvailableTimestamp: number = 0) {
371371
this._env = env;
372372
this._scriptLoader = scriptLoader;
373373
this._loaderAvailableTimestamp = loaderAvailableTimestamp;
@@ -387,7 +387,9 @@ namespace AMDLoader {
387387
this._buildInfoDefineStack = [];
388388
this._buildInfoDependencies = [];
389389

390-
this._requireFunc.moduleManager = this;
390+
if (this._requireFunc) {
391+
this._requireFunc.moduleManager = this;
392+
}
391393
}
392394

393395
public reset(): ModuleManager {
@@ -399,7 +401,7 @@ namespace AMDLoader {
399401
}
400402

401403
public getGlobalAMDRequireFunc(): IRequireFunc {
402-
return this._requireFunc;
404+
return this._requireFunc!;
403405
}
404406

405407
private static _findRelevantLocationInStack(needle: string, stack: string): IPosition {

src/loader.d.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,10 @@ declare namespace AMDLoader {
183183
writeDelay?: number;
184184
}
185185
interface IConfigurationOptions {
186+
/**
187+
* Allow module ids to end with .js
188+
*/
189+
allowJsExtension?: boolean;
186190
/**
187191
* The prefix that will be aplied to all modules when they are resolved to a location
188192
*/
@@ -267,6 +271,7 @@ declare namespace AMDLoader {
267271
nodeCachedData?: INodeCachedDataConfiguration;
268272
}
269273
interface IValidatedConfigurationOptions extends IConfigurationOptions {
274+
allowJsExtension: boolean;
270275
baseUrl: string;
271276
paths: {
272277
[path: string]: any;
@@ -321,7 +326,7 @@ declare namespace AMDLoader {
321326
/**
322327
* Transform a module id to a location. Appends .js to module ids
323328
*/
324-
moduleIdToPaths(moduleId: string): string[];
329+
moduleIdToPaths(moduleIdOrPath: string): string[];
325330
/**
326331
* Transform a module id or url to a location.
327332
*/
@@ -508,7 +513,7 @@ declare namespace AMDLoader {
508513
private _buildInfoPath;
509514
private _buildInfoDefineStack;
510515
private _buildInfoDependencies;
511-
constructor(env: Environment, scriptLoader: IScriptLoader, defineFunc: IDefineFunc, requireFunc: IRequireFunc, loaderAvailableTimestamp?: number);
516+
constructor(env: Environment, scriptLoader: IScriptLoader, defineFunc: IDefineFunc, requireFunc: IRequireFunc | null, loaderAvailableTimestamp?: number);
512517
reset(): ModuleManager;
513518
getGlobalAMDDefineFunc(): IDefineFunc;
514519
getGlobalAMDRequireFunc(): IRequireFunc;

src/loader.js

+14-6
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,9 @@ var AMDLoader;
254254
}
255255
}
256256
options = options || {};
257+
if (typeof options.allowJsExtension !== 'boolean') {
258+
options.allowJsExtension = false;
259+
}
257260
if (typeof options.baseUrl !== 'string') {
258261
options.baseUrl = '';
259262
}
@@ -425,10 +428,10 @@ var AMDLoader;
425428
/**
426429
* Transform a module id to a location. Appends .js to module ids
427430
*/
428-
moduleIdToPaths(moduleId) {
431+
moduleIdToPaths(moduleIdOrPath) {
429432
if (this._env.isNode) {
430433
const isNodeModule = (this.options.amdModulesPattern instanceof RegExp
431-
&& !this.options.amdModulesPattern.test(moduleId));
434+
&& !this.options.amdModulesPattern.test(moduleIdOrPath));
432435
if (isNodeModule) {
433436
// This is a node module...
434437
if (this.isBuild()) {
@@ -437,13 +440,16 @@ var AMDLoader;
437440
}
438441
else {
439442
// ...and at runtime we create a `shortcut`-path
440-
return ['node|' + moduleId];
443+
return ['node|' + moduleIdOrPath];
441444
}
442445
}
443446
}
444-
let result = moduleId;
447+
const isAbsolutePath = AMDLoader.Utilities.isAbsolutePath(moduleIdOrPath);
448+
const isJSFilePath = (this.options.allowJsExtension ? false : AMDLoader.Utilities.endsWith(moduleIdOrPath, '.js'));
449+
const isModuleId = !(isAbsolutePath || isJSFilePath);
450+
let result = moduleIdOrPath;
445451
let results;
446-
if (!AMDLoader.Utilities.endsWith(result, '.js') && !AMDLoader.Utilities.isAbsolutePath(result)) {
452+
if (isModuleId) {
447453
results = this._applyPaths(result);
448454
for (let i = 0, len = results.length; i < len; i++) {
449455
if (this.isBuild() && results[i] === 'empty:') {
@@ -1248,7 +1254,9 @@ var AMDLoader;
12481254
this._buildInfoPath = [];
12491255
this._buildInfoDefineStack = [];
12501256
this._buildInfoDependencies = [];
1251-
this._requireFunc.moduleManager = this;
1257+
if (this._requireFunc) {
1258+
this._requireFunc.moduleManager = this;
1259+
}
12521260
}
12531261
reset() {
12541262
return new ModuleManager(this._env, this._scriptLoader, this._defineFunc, this._requireFunc, this._loaderAvailableTimestamp);

tests/loader.test.js

+63
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ function assertConfigurationIs(actual, expected) {
1717
QUnit.test('Default configuration', () => {
1818
var result = loader.ConfigurationOptionsUtil.mergeConfigurationOptions();
1919
assertConfigurationIs(result, {
20+
allowJsExtension: false,
2021
baseUrl: '',
2122
catchError: false,
2223
ignoreDuplicateModules: [],
@@ -31,6 +32,7 @@ QUnit.test('Default configuration', () => {
3132
});
3233
function createSimpleKnownConfigurationOptions() {
3334
return loader.ConfigurationOptionsUtil.mergeConfigurationOptions({
35+
allowJsExtension: false,
3436
baseUrl: 'myBaseUrl',
3537
catchError: true,
3638
ignoreDuplicateModules: ['a'],
@@ -46,6 +48,7 @@ function createSimpleKnownConfigurationOptions() {
4648
QUnit.test('Simple known configuration options', () => {
4749
var result = createSimpleKnownConfigurationOptions();
4850
assertConfigurationIs(result, {
51+
allowJsExtension: false,
4952
baseUrl: 'myBaseUrl/',
5053
catchError: true,
5154
ignoreDuplicateModules: ['a'],
@@ -64,6 +67,7 @@ QUnit.test('Overwriting known configuration options', () => {
6467
baseUrl: ''
6568
}, createSimpleKnownConfigurationOptions());
6669
assertConfigurationIs(result, {
70+
allowJsExtension: false,
6771
baseUrl: '',
6872
catchError: true,
6973
ignoreDuplicateModules: ['a'],
@@ -80,6 +84,7 @@ QUnit.test('Overwriting known configuration options', () => {
8084
baseUrl: '/'
8185
}, createSimpleKnownConfigurationOptions());
8286
assertConfigurationIs(result, {
87+
allowJsExtension: false,
8388
baseUrl: '/',
8489
catchError: true,
8590
ignoreDuplicateModules: ['a'],
@@ -96,6 +101,7 @@ QUnit.test('Overwriting known configuration options', () => {
96101
catchError: false
97102
}, createSimpleKnownConfigurationOptions());
98103
assertConfigurationIs(result, {
104+
allowJsExtension: false,
99105
baseUrl: 'myBaseUrl/',
100106
catchError: false,
101107
ignoreDuplicateModules: ['a'],
@@ -112,6 +118,7 @@ QUnit.test('Overwriting known configuration options', () => {
112118
ignoreDuplicateModules: ['b']
113119
}, createSimpleKnownConfigurationOptions());
114120
assertConfigurationIs(result, {
121+
allowJsExtension: false,
115122
baseUrl: 'myBaseUrl/',
116123
catchError: true,
117124
ignoreDuplicateModules: ['a', 'b'],
@@ -128,6 +135,7 @@ QUnit.test('Overwriting known configuration options', () => {
128135
paths: { 'a': 'c' }
129136
}, createSimpleKnownConfigurationOptions());
130137
assertConfigurationIs(result, {
138+
allowJsExtension: false,
131139
baseUrl: 'myBaseUrl/',
132140
catchError: true,
133141
ignoreDuplicateModules: ['a'],
@@ -144,6 +152,7 @@ QUnit.test('Overwriting known configuration options', () => {
144152
config: { 'e': {} }
145153
}, createSimpleKnownConfigurationOptions());
146154
assertConfigurationIs(result, {
155+
allowJsExtension: false,
147156
baseUrl: 'myBaseUrl/',
148157
catchError: true,
149158
ignoreDuplicateModules: ['a'],
@@ -160,6 +169,7 @@ QUnit.test('Overwriting known configuration options', () => {
160169
config: { 'd': { 'a': 'a' } }
161170
}, createSimpleKnownConfigurationOptions());
162171
assertConfigurationIs(result, {
172+
allowJsExtension: false,
163173
baseUrl: 'myBaseUrl/',
164174
catchError: true,
165175
ignoreDuplicateModules: ['a'],
@@ -175,6 +185,7 @@ QUnit.test('Overwriting known configuration options', () => {
175185
QUnit.test('Overwriting unknown configuration options', () => {
176186
var result = loader.ConfigurationOptionsUtil.mergeConfigurationOptions();
177187
assertConfigurationIs(result, {
188+
allowJsExtension: false,
178189
baseUrl: '',
179190
catchError: false,
180191
ignoreDuplicateModules: [],
@@ -191,6 +202,7 @@ QUnit.test('Overwriting unknown configuration options', () => {
191202
unknownKey1: 'value1'
192203
}, result);
193204
assertConfigurationIs(result, {
205+
allowJsExtension: false,
194206
baseUrl: '',
195207
catchError: false,
196208
ignoreDuplicateModules: [],
@@ -208,6 +220,7 @@ QUnit.test('Overwriting unknown configuration options', () => {
208220
unknownKey2: 'value2'
209221
}, result);
210222
assertConfigurationIs(result, {
223+
allowJsExtension: false,
211224
baseUrl: '',
212225
catchError: false,
213226
ignoreDuplicateModules: [],
@@ -226,6 +239,7 @@ QUnit.test('Overwriting unknown configuration options', () => {
226239
unknownKey2: 'new-value2'
227240
}, result);
228241
assertConfigurationIs(result, {
242+
allowJsExtension: false,
229243
baseUrl: '',
230244
catchError: false,
231245
ignoreDuplicateModules: [],
@@ -277,6 +291,55 @@ QUnit.test('moduleIdToPath', () => {
277291
QUnit.equal(config.moduleIdToPaths('https://a/b/c/d'), 'https://a/b/c/d.js?suffix');
278292
QUnit.equal(config.moduleIdToPaths('https://a'), 'https://a.js?suffix');
279293
});
294+
QUnit.test('moduleIdToPath with allowJsExtension', () => {
295+
var config = new loader.Configuration(new loader.Environment(), {
296+
allowJsExtension: true,
297+
baseUrl: 'prefix',
298+
urlArgs: 'suffix',
299+
paths: {
300+
'a': 'newa',
301+
'knockout': 'http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js',
302+
'knockout.js': 'http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js',
303+
'editor': '/src/editor'
304+
}
305+
});
306+
// baseUrl is applied
307+
QUnit.equal(config.moduleIdToPaths('b/c/d'), 'prefix/b/c/d.js?suffix');
308+
QUnit.equal(config.moduleIdToPaths('b/c/d.js'), 'prefix/b/c/d.js?suffix');
309+
// paths rules are applied
310+
QUnit.equal(config.moduleIdToPaths('a'), 'prefix/newa.js?suffix');
311+
QUnit.equal(config.moduleIdToPaths('a.js'), 'prefix/newa.js?suffix');
312+
QUnit.equal(config.moduleIdToPaths('a/b/c/d'), 'prefix/newa/b/c/d.js?suffix');
313+
QUnit.equal(config.moduleIdToPaths('a/b/c/d.js'), 'prefix/newa/b/c/d.js?suffix');
314+
// paths rules check if value is an absolute path
315+
QUnit.equal(config.moduleIdToPaths('knockout'), 'http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js?suffix');
316+
QUnit.equal(config.moduleIdToPaths('knockout.js'), 'http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js?suffix');
317+
// modules redirected to / still get .js appended
318+
QUnit.equal(config.moduleIdToPaths('editor/x'), '/src/editor/x.js?suffix');
319+
QUnit.equal(config.moduleIdToPaths('editor/x.js'), '/src/editor/x.js?suffix');
320+
// modules starting with / skip baseUrl + paths rules
321+
QUnit.equal(config.moduleIdToPaths('/b/c/d'), '/b/c/d.js?suffix');
322+
QUnit.equal(config.moduleIdToPaths('/b/c/d.js'), '/b/c/d.js?suffix');
323+
QUnit.equal(config.moduleIdToPaths('/a/b/c/d'), '/a/b/c/d.js?suffix');
324+
QUnit.equal(config.moduleIdToPaths('/a/b/c/d.js'), '/a/b/c/d.js?suffix');
325+
QUnit.equal(config.moduleIdToPaths('/a'), '/a.js?suffix');
326+
QUnit.equal(config.moduleIdToPaths('/a.js'), '/a.js?suffix');
327+
// modules starting with http:// or https:// skip baseUrl + paths rules
328+
QUnit.equal(config.moduleIdToPaths('file:///c:/a/b/c'), 'file:///c:/a/b/c.js?suffix');
329+
QUnit.equal(config.moduleIdToPaths('file:///c:/a/b/c.js'), 'file:///c:/a/b/c.js?suffix');
330+
QUnit.equal(config.moduleIdToPaths('http://b/c/d'), 'http://b/c/d.js?suffix');
331+
QUnit.equal(config.moduleIdToPaths('http://b/c/d.js'), 'http://b/c/d.js?suffix');
332+
QUnit.equal(config.moduleIdToPaths('http://a/b/c/d'), 'http://a/b/c/d.js?suffix');
333+
QUnit.equal(config.moduleIdToPaths('http://a/b/c/d.js'), 'http://a/b/c/d.js?suffix');
334+
QUnit.equal(config.moduleIdToPaths('http://a'), 'http://a.js?suffix');
335+
QUnit.equal(config.moduleIdToPaths('http://a.js'), 'http://a.js?suffix');
336+
QUnit.equal(config.moduleIdToPaths('https://b/c/d'), 'https://b/c/d.js?suffix');
337+
QUnit.equal(config.moduleIdToPaths('https://b/c/d.js'), 'https://b/c/d.js?suffix');
338+
QUnit.equal(config.moduleIdToPaths('https://a/b/c/d'), 'https://a/b/c/d.js?suffix');
339+
QUnit.equal(config.moduleIdToPaths('https://a/b/c/d.js'), 'https://a/b/c/d.js?suffix');
340+
QUnit.equal(config.moduleIdToPaths('https://a'), 'https://a.js?suffix');
341+
QUnit.equal(config.moduleIdToPaths('https://a.js'), 'https://a.js?suffix');
342+
});
280343
QUnit.test('requireToUrl', () => {
281344
var config = new loader.Configuration(new loader.Environment(), {
282345
baseUrl: 'prefix',

0 commit comments

Comments
 (0)