diff --git a/src/core/configuration.ts b/src/core/configuration.ts index 924766d..2a0ca57 100644 --- a/src/core/configuration.ts +++ b/src/core/configuration.ts @@ -114,6 +114,10 @@ namespace AMDLoader { }; export interface IConfigurationOptions { + /** + * Allow module ids to end with .js + */ + allowJsExtension?: boolean; /** * The prefix that will be aplied to all modules when they are resolved to a location */ @@ -193,6 +197,7 @@ namespace AMDLoader { } export interface IValidatedConfigurationOptions extends IConfigurationOptions { + allowJsExtension: boolean; baseUrl: string; paths: { [path: string]: any; }; config: { [moduleId: string]: IModuleConfiguration }; @@ -232,6 +237,9 @@ namespace AMDLoader { } options = options || {}; + if (typeof options.allowJsExtension !== 'boolean') { + options.allowJsExtension = false; + } if (typeof options.baseUrl !== 'string') { options.baseUrl = ''; } @@ -428,12 +436,12 @@ namespace AMDLoader { /** * Transform a module id to a location. Appends .js to module ids */ - public moduleIdToPaths(moduleId: string): string[] { + public moduleIdToPaths(moduleIdOrPath: string): string[] { if (this._env.isNode) { const isNodeModule = ( this.options.amdModulesPattern instanceof RegExp - && !this.options.amdModulesPattern.test(moduleId) + && !this.options.amdModulesPattern.test(moduleIdOrPath) ); if (isNodeModule) { @@ -443,15 +451,18 @@ namespace AMDLoader { return ['empty:']; } else { // ...and at runtime we create a `shortcut`-path - return ['node|' + moduleId]; + return ['node|' + moduleIdOrPath]; } } } - let result = moduleId; + const isAbsolutePath = Utilities.isAbsolutePath(moduleIdOrPath); + const isJSFilePath = (this.options.allowJsExtension ? false : Utilities.endsWith(moduleIdOrPath, '.js')); + const isModuleId = !(isAbsolutePath || isJSFilePath); + let result = moduleIdOrPath; let results: string[]; - if (!Utilities.endsWith(result, '.js') && !Utilities.isAbsolutePath(result)) { + if (isModuleId) { results = this._applyPaths(result); for (let i = 0, len = results.length; i < len; i++) { diff --git a/src/loader.d.ts b/src/loader.d.ts index f0cbc28..bef8464 100644 --- a/src/loader.d.ts +++ b/src/loader.d.ts @@ -183,6 +183,10 @@ declare namespace AMDLoader { writeDelay?: number; } interface IConfigurationOptions { + /** + * Allow module ids to end with .js + */ + allowJsExtension?: boolean; /** * The prefix that will be aplied to all modules when they are resolved to a location */ @@ -267,6 +271,7 @@ declare namespace AMDLoader { nodeCachedData?: INodeCachedDataConfiguration; } interface IValidatedConfigurationOptions extends IConfigurationOptions { + allowJsExtension: boolean; baseUrl: string; paths: { [path: string]: any; @@ -321,7 +326,7 @@ declare namespace AMDLoader { /** * Transform a module id to a location. Appends .js to module ids */ - moduleIdToPaths(moduleId: string): string[]; + moduleIdToPaths(moduleIdOrPath: string): string[]; /** * Transform a module id or url to a location. */ diff --git a/src/loader.js b/src/loader.js index 8017967..c6faa37 100644 --- a/src/loader.js +++ b/src/loader.js @@ -254,6 +254,9 @@ var AMDLoader; } } options = options || {}; + if (typeof options.allowJsExtension !== 'boolean') { + options.allowJsExtension = false; + } if (typeof options.baseUrl !== 'string') { options.baseUrl = ''; } @@ -425,10 +428,10 @@ var AMDLoader; /** * Transform a module id to a location. Appends .js to module ids */ - moduleIdToPaths(moduleId) { + moduleIdToPaths(moduleIdOrPath) { if (this._env.isNode) { const isNodeModule = (this.options.amdModulesPattern instanceof RegExp - && !this.options.amdModulesPattern.test(moduleId)); + && !this.options.amdModulesPattern.test(moduleIdOrPath)); if (isNodeModule) { // This is a node module... if (this.isBuild()) { @@ -437,13 +440,16 @@ var AMDLoader; } else { // ...and at runtime we create a `shortcut`-path - return ['node|' + moduleId]; + return ['node|' + moduleIdOrPath]; } } } - let result = moduleId; + const isAbsolutePath = AMDLoader.Utilities.isAbsolutePath(moduleIdOrPath); + const isJSFilePath = (this.options.allowJsExtension ? false : AMDLoader.Utilities.endsWith(moduleIdOrPath, '.js')); + const isModuleId = !(isAbsolutePath || isJSFilePath); + let result = moduleIdOrPath; let results; - if (!AMDLoader.Utilities.isAbsolutePath(result)) { + if (isModuleId) { results = this._applyPaths(result); for (let i = 0, len = results.length; i < len; i++) { if (this.isBuild() && results[i] === 'empty:') { diff --git a/tests/loader.test.js b/tests/loader.test.js index 102b9b7..0a25d33 100644 --- a/tests/loader.test.js +++ b/tests/loader.test.js @@ -17,6 +17,7 @@ function assertConfigurationIs(actual, expected) { QUnit.test('Default configuration', () => { var result = loader.ConfigurationOptionsUtil.mergeConfigurationOptions(); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: '', catchError: false, ignoreDuplicateModules: [], @@ -31,6 +32,7 @@ QUnit.test('Default configuration', () => { }); function createSimpleKnownConfigurationOptions() { return loader.ConfigurationOptionsUtil.mergeConfigurationOptions({ + allowJsExtension: false, baseUrl: 'myBaseUrl', catchError: true, ignoreDuplicateModules: ['a'], @@ -46,6 +48,7 @@ function createSimpleKnownConfigurationOptions() { QUnit.test('Simple known configuration options', () => { var result = createSimpleKnownConfigurationOptions(); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: 'myBaseUrl/', catchError: true, ignoreDuplicateModules: ['a'], @@ -64,6 +67,7 @@ QUnit.test('Overwriting known configuration options', () => { baseUrl: '' }, createSimpleKnownConfigurationOptions()); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: '', catchError: true, ignoreDuplicateModules: ['a'], @@ -80,6 +84,7 @@ QUnit.test('Overwriting known configuration options', () => { baseUrl: '/' }, createSimpleKnownConfigurationOptions()); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: '/', catchError: true, ignoreDuplicateModules: ['a'], @@ -96,6 +101,7 @@ QUnit.test('Overwriting known configuration options', () => { catchError: false }, createSimpleKnownConfigurationOptions()); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: 'myBaseUrl/', catchError: false, ignoreDuplicateModules: ['a'], @@ -112,6 +118,7 @@ QUnit.test('Overwriting known configuration options', () => { ignoreDuplicateModules: ['b'] }, createSimpleKnownConfigurationOptions()); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: 'myBaseUrl/', catchError: true, ignoreDuplicateModules: ['a', 'b'], @@ -128,6 +135,7 @@ QUnit.test('Overwriting known configuration options', () => { paths: { 'a': 'c' } }, createSimpleKnownConfigurationOptions()); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: 'myBaseUrl/', catchError: true, ignoreDuplicateModules: ['a'], @@ -144,6 +152,7 @@ QUnit.test('Overwriting known configuration options', () => { config: { 'e': {} } }, createSimpleKnownConfigurationOptions()); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: 'myBaseUrl/', catchError: true, ignoreDuplicateModules: ['a'], @@ -160,6 +169,7 @@ QUnit.test('Overwriting known configuration options', () => { config: { 'd': { 'a': 'a' } } }, createSimpleKnownConfigurationOptions()); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: 'myBaseUrl/', catchError: true, ignoreDuplicateModules: ['a'], @@ -175,6 +185,7 @@ QUnit.test('Overwriting known configuration options', () => { QUnit.test('Overwriting unknown configuration options', () => { var result = loader.ConfigurationOptionsUtil.mergeConfigurationOptions(); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: '', catchError: false, ignoreDuplicateModules: [], @@ -191,6 +202,7 @@ QUnit.test('Overwriting unknown configuration options', () => { unknownKey1: 'value1' }, result); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: '', catchError: false, ignoreDuplicateModules: [], @@ -208,6 +220,7 @@ QUnit.test('Overwriting unknown configuration options', () => { unknownKey2: 'value2' }, result); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: '', catchError: false, ignoreDuplicateModules: [], @@ -226,6 +239,7 @@ QUnit.test('Overwriting unknown configuration options', () => { unknownKey2: 'new-value2' }, result); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: '', catchError: false, ignoreDuplicateModules: [], @@ -277,6 +291,55 @@ QUnit.test('moduleIdToPath', () => { QUnit.equal(config.moduleIdToPaths('https://a/b/c/d'), 'https://a/b/c/d.js?suffix'); QUnit.equal(config.moduleIdToPaths('https://a'), 'https://a.js?suffix'); }); +QUnit.test('moduleIdToPath with allowJsExtension', () => { + var config = new loader.Configuration(new loader.Environment(), { + allowJsExtension: true, + baseUrl: 'prefix', + urlArgs: 'suffix', + paths: { + 'a': 'newa', + 'knockout': 'http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js', + 'knockout.js': 'http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js', + 'editor': '/src/editor' + } + }); + // baseUrl is applied + QUnit.equal(config.moduleIdToPaths('b/c/d'), 'prefix/b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('b/c/d.js'), 'prefix/b/c/d.js?suffix'); + // paths rules are applied + QUnit.equal(config.moduleIdToPaths('a'), 'prefix/newa.js?suffix'); + QUnit.equal(config.moduleIdToPaths('a.js'), 'prefix/newa.js?suffix'); + QUnit.equal(config.moduleIdToPaths('a/b/c/d'), 'prefix/newa/b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('a/b/c/d.js'), 'prefix/newa/b/c/d.js?suffix'); + // paths rules check if value is an absolute path + QUnit.equal(config.moduleIdToPaths('knockout'), 'http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js?suffix'); + QUnit.equal(config.moduleIdToPaths('knockout.js'), 'http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js?suffix'); + // modules redirected to / still get .js appended + QUnit.equal(config.moduleIdToPaths('editor/x'), '/src/editor/x.js?suffix'); + QUnit.equal(config.moduleIdToPaths('editor/x.js'), '/src/editor/x.js?suffix'); + // modules starting with / skip baseUrl + paths rules + QUnit.equal(config.moduleIdToPaths('/b/c/d'), '/b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('/b/c/d.js'), '/b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('/a/b/c/d'), '/a/b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('/a/b/c/d.js'), '/a/b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('/a'), '/a.js?suffix'); + QUnit.equal(config.moduleIdToPaths('/a.js'), '/a.js?suffix'); + // modules starting with http:// or https:// skip baseUrl + paths rules + QUnit.equal(config.moduleIdToPaths('file:///c:/a/b/c'), 'file:///c:/a/b/c.js?suffix'); + QUnit.equal(config.moduleIdToPaths('file:///c:/a/b/c.js'), 'file:///c:/a/b/c.js?suffix'); + QUnit.equal(config.moduleIdToPaths('http://b/c/d'), 'http://b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('http://b/c/d.js'), 'http://b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('http://a/b/c/d'), 'http://a/b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('http://a/b/c/d.js'), 'http://a/b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('http://a'), 'http://a.js?suffix'); + QUnit.equal(config.moduleIdToPaths('http://a.js'), 'http://a.js?suffix'); + QUnit.equal(config.moduleIdToPaths('https://b/c/d'), 'https://b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('https://b/c/d.js'), 'https://b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('https://a/b/c/d'), 'https://a/b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('https://a/b/c/d.js'), 'https://a/b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('https://a'), 'https://a.js?suffix'); + QUnit.equal(config.moduleIdToPaths('https://a.js'), 'https://a.js?suffix'); +}); QUnit.test('requireToUrl', () => { var config = new loader.Configuration(new loader.Environment(), { baseUrl: 'prefix', diff --git a/tests/loader.test.ts b/tests/loader.test.ts index 786a4ff..9ce00b6 100644 --- a/tests/loader.test.ts +++ b/tests/loader.test.ts @@ -21,6 +21,7 @@ function assertConfigurationIs(actual: loader.IConfigurationOptions, expected: l QUnit.test('Default configuration', () => { var result = loader.ConfigurationOptionsUtil.mergeConfigurationOptions(); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: '', catchError: false, ignoreDuplicateModules: [], @@ -36,6 +37,7 @@ QUnit.test('Default configuration', () => { function createSimpleKnownConfigurationOptions(): loader.IConfigurationOptions { return loader.ConfigurationOptionsUtil.mergeConfigurationOptions({ + allowJsExtension: false, baseUrl: 'myBaseUrl', catchError: true, ignoreDuplicateModules: ['a'], @@ -52,6 +54,7 @@ function createSimpleKnownConfigurationOptions(): loader.IConfigurationOptions { QUnit.test('Simple known configuration options', () => { var result = createSimpleKnownConfigurationOptions(); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: 'myBaseUrl/', catchError: true, ignoreDuplicateModules: ['a'], @@ -71,6 +74,7 @@ QUnit.test('Overwriting known configuration options', () => { baseUrl: '' }, createSimpleKnownConfigurationOptions()); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: '', catchError: true, ignoreDuplicateModules: ['a'], @@ -88,6 +92,7 @@ QUnit.test('Overwriting known configuration options', () => { baseUrl: '/' }, createSimpleKnownConfigurationOptions()); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: '/', catchError: true, ignoreDuplicateModules: ['a'], @@ -105,6 +110,7 @@ QUnit.test('Overwriting known configuration options', () => { catchError: false }, createSimpleKnownConfigurationOptions()); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: 'myBaseUrl/', catchError: false, ignoreDuplicateModules: ['a'], @@ -122,6 +128,7 @@ QUnit.test('Overwriting known configuration options', () => { ignoreDuplicateModules: ['b'] }, createSimpleKnownConfigurationOptions()); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: 'myBaseUrl/', catchError: true, ignoreDuplicateModules: ['a', 'b'], @@ -139,6 +146,7 @@ QUnit.test('Overwriting known configuration options', () => { paths: { 'a': 'c' } }, createSimpleKnownConfigurationOptions()); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: 'myBaseUrl/', catchError: true, ignoreDuplicateModules: ['a'], @@ -156,6 +164,7 @@ QUnit.test('Overwriting known configuration options', () => { config: { 'e': {} } }, createSimpleKnownConfigurationOptions()); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: 'myBaseUrl/', catchError: true, ignoreDuplicateModules: ['a'], @@ -173,6 +182,7 @@ QUnit.test('Overwriting known configuration options', () => { config: { 'd': { 'a': 'a' } } }, createSimpleKnownConfigurationOptions()); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: 'myBaseUrl/', catchError: true, ignoreDuplicateModules: ['a'], @@ -189,6 +199,7 @@ QUnit.test('Overwriting known configuration options', () => { QUnit.test('Overwriting unknown configuration options', () => { var result = loader.ConfigurationOptionsUtil.mergeConfigurationOptions(); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: '', catchError: false, ignoreDuplicateModules: [], @@ -206,6 +217,7 @@ QUnit.test('Overwriting unknown configuration options', () => { unknownKey1: 'value1' }, result); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: '', catchError: false, ignoreDuplicateModules: [], @@ -224,6 +236,7 @@ QUnit.test('Overwriting unknown configuration options', () => { unknownKey2: 'value2' }, result); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: '', catchError: false, ignoreDuplicateModules: [], @@ -243,6 +256,7 @@ QUnit.test('Overwriting unknown configuration options', () => { unknownKey2: 'new-value2' }, result); assertConfigurationIs(result, { + allowJsExtension: false, baseUrl: '', catchError: false, ignoreDuplicateModules: [], @@ -304,6 +318,62 @@ QUnit.test('moduleIdToPath', () => { QUnit.equal(config.moduleIdToPaths('https://a'), 'https://a.js?suffix'); }); +QUnit.test('moduleIdToPath with allowJsExtension', () => { + var config = new loader.Configuration(new loader.Environment(), { + allowJsExtension: true, + baseUrl: 'prefix', + urlArgs: 'suffix', + paths: { + 'a': 'newa', + 'knockout': 'http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js', + 'knockout.js': 'http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js', + 'editor': '/src/editor' + } + }); + + // baseUrl is applied + QUnit.equal(config.moduleIdToPaths('b/c/d'), 'prefix/b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('b/c/d.js'), 'prefix/b/c/d.js?suffix'); + + // paths rules are applied + QUnit.equal(config.moduleIdToPaths('a'), 'prefix/newa.js?suffix'); + QUnit.equal(config.moduleIdToPaths('a.js'), 'prefix/newa.js?suffix'); + QUnit.equal(config.moduleIdToPaths('a/b/c/d'), 'prefix/newa/b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('a/b/c/d.js'), 'prefix/newa/b/c/d.js?suffix'); + + // paths rules check if value is an absolute path + QUnit.equal(config.moduleIdToPaths('knockout'), 'http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js?suffix'); + QUnit.equal(config.moduleIdToPaths('knockout.js'), 'http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js?suffix'); + + // modules redirected to / still get .js appended + QUnit.equal(config.moduleIdToPaths('editor/x'), '/src/editor/x.js?suffix'); + QUnit.equal(config.moduleIdToPaths('editor/x.js'), '/src/editor/x.js?suffix'); + + // modules starting with / skip baseUrl + paths rules + QUnit.equal(config.moduleIdToPaths('/b/c/d'), '/b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('/b/c/d.js'), '/b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('/a/b/c/d'), '/a/b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('/a/b/c/d.js'), '/a/b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('/a'), '/a.js?suffix'); + QUnit.equal(config.moduleIdToPaths('/a.js'), '/a.js?suffix'); + + // modules starting with http:// or https:// skip baseUrl + paths rules + QUnit.equal(config.moduleIdToPaths('file:///c:/a/b/c'), 'file:///c:/a/b/c.js?suffix'); + QUnit.equal(config.moduleIdToPaths('file:///c:/a/b/c.js'), 'file:///c:/a/b/c.js?suffix'); + QUnit.equal(config.moduleIdToPaths('http://b/c/d'), 'http://b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('http://b/c/d.js'), 'http://b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('http://a/b/c/d'), 'http://a/b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('http://a/b/c/d.js'), 'http://a/b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('http://a'), 'http://a.js?suffix'); + QUnit.equal(config.moduleIdToPaths('http://a.js'), 'http://a.js?suffix'); + QUnit.equal(config.moduleIdToPaths('https://b/c/d'), 'https://b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('https://b/c/d.js'), 'https://b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('https://a/b/c/d'), 'https://a/b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('https://a/b/c/d.js'), 'https://a/b/c/d.js?suffix'); + QUnit.equal(config.moduleIdToPaths('https://a'), 'https://a.js?suffix'); + QUnit.equal(config.moduleIdToPaths('https://a.js'), 'https://a.js?suffix'); +}); + QUnit.test('requireToUrl', () => { var config = new loader.Configuration(new loader.Environment(), { baseUrl: 'prefix',