Skip to content

Commit

Permalink
Slim down to a minimal core
Browse files Browse the repository at this point in the history
This re-focuses the import maps explainer and specification on a minimal core which is more likely to be implementable and agreeable in the near future. It removes multiple import map support, fallback support, and built-in module support for now. The explainer contains a "Further work" section which details these and other potential future work.

In terms of normative changes:

* The parser now rejects non-string addresses, so arrays and null are no longer valid
* The resolver becomes much simpler due to this change, as well as the removal of built-in module support
* There is no more procedure for merging import maps
* Script fetches only wait on a single "pending import map script" instead of a "list of pending import map scripts"
  • Loading branch information
domenic committed Sep 23, 2019
1 parent 93f94c6 commit 6f99d47
Show file tree
Hide file tree
Showing 18 changed files with 241 additions and 1,372 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/reference-implementation/node_modules/
/reference-implementation/coverage/
/out/
/spec.html
/deploy_key
546 changes: 75 additions & 471 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion reference-implementation/__tests__/helpers/parsing.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ exports.expectBad = (input, baseURL, warnings = []) => {
checkWarnings();
};

exports.expectWarnings = (input, baseURL, output, warnings = []) => {
exports.expectWarnings = (input, baseURL, output, warnings) => {
const checkWarnings = testWarningHandler(warnings);
expect(parseFromString(input, baseURL)).toEqual(output);

Expand Down
217 changes: 21 additions & 196 deletions reference-implementation/__tests__/parsing-addresses.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use strict';
const { expectSpecifierMap } = require('./helpers/parsing.js');
const { BUILT_IN_MODULE_SCHEME } = require('../lib/utils.js');

describe('Relative URL-like addresses', () => {
it('should accept strings prefixed with ./, ../, or /', () => {
Expand All @@ -12,9 +11,9 @@ describe('Relative URL-like addresses', () => {
}`,
'https://base.example/path1/path2/path3',
{
dotSlash: [expect.toMatchURL('https://base.example/path1/path2/foo')],
dotDotSlash: [expect.toMatchURL('https://base.example/path1/foo')],
slash: [expect.toMatchURL('https://base.example/foo')]
dotSlash: expect.toMatchURL('https://base.example/path1/path2/foo'),
dotDotSlash: expect.toMatchURL('https://base.example/path1/foo'),
slash: expect.toMatchURL('https://base.example/foo')
}
);
});
Expand All @@ -28,9 +27,6 @@ describe('Relative URL-like addresses', () => {
}`,
'data:text/html,test',
{
dotSlash: [],
dotDotSlash: [],
slash: []
},
[
`Invalid address "./foo" for the specifier key "dotSlash".`,
Expand All @@ -49,9 +45,9 @@ describe('Relative URL-like addresses', () => {
}`,
'https://base.example/path1/path2/path3',
{
dotSlash: [expect.toMatchURL('https://base.example/path1/path2/')],
dotDotSlash: [expect.toMatchURL('https://base.example/path1/')],
slash: [expect.toMatchURL('https://base.example/')]
dotSlash: expect.toMatchURL('https://base.example/path1/path2/'),
dotDotSlash: expect.toMatchURL('https://base.example/path1/'),
slash: expect.toMatchURL('https://base.example/')
}
);
});
Expand All @@ -69,13 +65,6 @@ describe('Relative URL-like addresses', () => {
}`,
'https://base.example/path1/path2/path3',
{
dotSlash1: [],
dotDotSlash1: [],
dotSlash2: [],
dotDotSlash2: [],
slash2: [],
dotSlash3: [],
dotDotSlash3: []
},
[
`Invalid address "%2E/" for the specifier key "dotSlash1".`,
Expand All @@ -90,49 +79,6 @@ describe('Relative URL-like addresses', () => {
});
});

describe('Built-in module addresses', () => {
it('should accept URLs using the built-in module scheme', () => {
expectSpecifierMap(
`{
"foo": "${BUILT_IN_MODULE_SCHEME}:foo"
}`,
'https://base.example/path1/path2/path3',
{
foo: [expect.toMatchURL(`${BUILT_IN_MODULE_SCHEME}:foo`)]
}
);
});

it('should ignore percent-encoded variants of the built-in module scheme', () => {
expectSpecifierMap(
`{
"foo": "${encodeURIComponent(BUILT_IN_MODULE_SCHEME + ':')}foo"
}`,
'https://base.example/path1/path2/path3',
{
foo: []
},
[`Invalid address "${encodeURIComponent(BUILT_IN_MODULE_SCHEME + ':')}foo" for the specifier key "foo".`]
);
});

it('should allow built-in module URLs that contain "/" or "\\"', () => {
expectSpecifierMap(
`{
"slashEnd": "${BUILT_IN_MODULE_SCHEME}:foo/",
"slashMiddle": "${BUILT_IN_MODULE_SCHEME}:foo/bar",
"backslash": "${BUILT_IN_MODULE_SCHEME}:foo\\\\baz"
}`,
'https://base.example/path1/path2/path3',
{
slashEnd: [expect.toMatchURL(`${BUILT_IN_MODULE_SCHEME}:foo/`)],
slashMiddle: [expect.toMatchURL(`${BUILT_IN_MODULE_SCHEME}:foo/bar`)],
backslash: [expect.toMatchURL(`${BUILT_IN_MODULE_SCHEME}:foo\\baz`)]
}
);
});
});

describe('Absolute URL addresses', () => {
it('should only accept absolute URL addresses with fetch schemes', () => {
expectSpecifierMap(
Expand All @@ -152,58 +98,14 @@ describe('Absolute URL addresses', () => {
}`,
'https://base.example/path1/path2/path3',
{
about: [expect.toMatchURL('about:good')],
blob: [expect.toMatchURL('blob:good')],
data: [expect.toMatchURL('data:good')],
file: [expect.toMatchURL('file:///good')],
filesystem: [expect.toMatchURL('filesystem:good')],
http: [expect.toMatchURL('http://good/')],
https: [expect.toMatchURL('https://good/')],
ftp: [expect.toMatchURL('ftp://good/')],
import: [],
mailto: [],
javascript: [],
wss: []
},
[
`Invalid address "import:bad" for the specifier key "import".`,
`Invalid address "mailto:bad" for the specifier key "mailto".`,
`Invalid address "javascript:bad" for the specifier key "javascript".`,
`Invalid address "wss:bad" for the specifier key "wss".`
]
);
});

it('should only accept absolute URL addresses with fetch schemes inside arrays', () => {
expectSpecifierMap(
`{
"about": ["about:good"],
"blob": ["blob:good"],
"data": ["data:good"],
"file": ["file:///good"],
"filesystem": ["filesystem:good"],
"http": ["http://good/"],
"https": ["https://good/"],
"ftp": ["ftp://good/"],
"import": ["import:bad"],
"mailto": ["mailto:bad"],
"javascript": ["javascript:bad"],
"wss": ["wss:bad"]
}`,
'https://base.example/path1/path2/path3',
{
about: [expect.toMatchURL('about:good')],
blob: [expect.toMatchURL('blob:good')],
data: [expect.toMatchURL('data:good')],
file: [expect.toMatchURL('file:///good')],
filesystem: [expect.toMatchURL('filesystem:good')],
http: [expect.toMatchURL('http://good/')],
https: [expect.toMatchURL('https://good/')],
ftp: [expect.toMatchURL('ftp://good/')],
import: [],
mailto: [],
javascript: [],
wss: []
about: expect.toMatchURL('about:good'),
blob: expect.toMatchURL('blob:good'),
data: expect.toMatchURL('data:good'),
file: expect.toMatchURL('file:///good'),
filesystem: expect.toMatchURL('filesystem:good'),
http: expect.toMatchURL('http://good/'),
https: expect.toMatchURL('https://good/'),
ftp: expect.toMatchURL('ftp://good/')
},
[
`Invalid address "import:bad" for the specifier key "import".`,
Expand All @@ -228,45 +130,11 @@ describe('Absolute URL addresses', () => {
}`,
'https://base.example/path1/path2/path3',
{
unparseable1: [],
unparseable2: [],
unparseable3: [],
invalidButParseable1: [expect.toMatchURL('https://example.org/')],
invalidButParseable2: [expect.toMatchURL('https://example.com///')],
prettyNormal: [expect.toMatchURL('https://example.net/')],
percentDecoding: [expect.toMatchURL('https://example.com/')],
noPercentDecoding: [expect.toMatchURL('https://example.com/%41')]
},
[
`Invalid address "https://ex ample.org/" for the specifier key "unparseable1".`,
`Invalid address "https://example.com:demo" for the specifier key "unparseable2".`,
`Invalid address "http://[www.example.com]/" for the specifier key "unparseable3".`
]
);
});

it('should parse absolute URLs, ignoring unparseable ones inside arrays', () => {
expectSpecifierMap(
`{
"unparseable1": ["https://ex ample.org/"],
"unparseable2": ["https://example.com:demo"],
"unparseable3": ["http://[www.example.com]/"],
"invalidButParseable1": ["https:example.org"],
"invalidButParseable2": ["https://///example.com///"],
"prettyNormal": ["https://example.net"],
"percentDecoding": ["https://ex%41mple.com/"],
"noPercentDecoding": ["https://example.com/%41"]
}`,
'https://base.example/path1/path2/path3',
{
unparseable1: [],
unparseable2: [],
unparseable3: [],
invalidButParseable1: [expect.toMatchURL('https://example.org/')],
invalidButParseable2: [expect.toMatchURL('https://example.com///')],
prettyNormal: [expect.toMatchURL('https://example.net/')],
percentDecoding: [expect.toMatchURL('https://example.com/')],
noPercentDecoding: [expect.toMatchURL('https://example.com/%41')]
invalidButParseable1: expect.toMatchURL('https://example.org/'),
invalidButParseable2: expect.toMatchURL('https://example.com///'),
prettyNormal: expect.toMatchURL('https://example.net/'),
percentDecoding: expect.toMatchURL('https://example.com/'),
noPercentDecoding: expect.toMatchURL('https://example.com/%41')
},
[
`Invalid address "https://ex ample.org/" for the specifier key "unparseable1".`,
Expand All @@ -281,54 +149,12 @@ describe('Failing addresses: mismatched trailing slashes', () => {
it('should warn for the simple case', () => {
expectSpecifierMap(
`{
"trailer/": "/notrailer",
"${BUILT_IN_MODULE_SCHEME}:trailer/": "/bim-notrailer"
}`,
'https://base.example/path1/path2/path3',
{
'trailer/': [],
[`${BUILT_IN_MODULE_SCHEME}:trailer/`]: []
},
[
`Invalid address "https://base.example/notrailer" for package specifier key "trailer/". Package addresses must end with "/".`,
`Invalid address "https://base.example/bim-notrailer" for package specifier key "${BUILT_IN_MODULE_SCHEME}:trailer/". Package addresses must end with "/".`
]
);
});

it('should warn for a mismatch alone in an array', () => {
expectSpecifierMap(
`{
"trailer/": ["/notrailer"],
"${BUILT_IN_MODULE_SCHEME}:trailer/": ["/bim-notrailer"]
}`,
'https://base.example/path1/path2/path3',
{
'trailer/': [],
[`${BUILT_IN_MODULE_SCHEME}:trailer/`]: []
},
[
`Invalid address "https://base.example/notrailer" for package specifier key "trailer/". Package addresses must end with "/".`,
`Invalid address "https://base.example/bim-notrailer" for package specifier key "${BUILT_IN_MODULE_SCHEME}:trailer/". Package addresses must end with "/".`
]
);
});

it('should warn for a mismatch alongside non-mismatches in an array', () => {
expectSpecifierMap(
`{
"trailer/": ["/atrailer/", "/notrailer"],
"${BUILT_IN_MODULE_SCHEME}:trailer/": ["/bim-atrailer/", "/bim-notrailer"]
"trailer/": "/notrailer"
}`,
'https://base.example/path1/path2/path3',
{
'trailer/': [expect.toMatchURL('https://base.example/atrailer/')],
[`${BUILT_IN_MODULE_SCHEME}:trailer/`]: [expect.toMatchURL('https://base.example/bim-atrailer/')]
},
[
`Invalid address "https://base.example/notrailer" for package specifier key "trailer/". Package addresses must end with "/".`,
`Invalid address "https://base.example/bim-notrailer" for package specifier key "${BUILT_IN_MODULE_SCHEME}:trailer/". Package addresses must end with "/".`
]
[`Invalid address "https://base.example/notrailer" for package specifier key "trailer/". Package addresses must end with "/".`]
);
});
});
Expand All @@ -342,7 +168,6 @@ describe('Other invalid addresses', () => {
}`,
'https://base.example/path1/path2/path3',
{
foo: []
},
[`Invalid address "${bad}" for the specifier key "foo".`]
);
Expand Down
52 changes: 6 additions & 46 deletions reference-implementation/__tests__/parsing-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,59 +45,35 @@ describe('Mismatching the top-level schema', () => {
});

describe('Mismatching the specifier map schema', () => {
const invalidAddressStrings = ['true', '1', '{}'];
const invalidInsideArrayStrings = ['null', 'true', '1', '{}', '[]'];
const invalidAddressStrings = ['null', 'true', '1', '{}', '[]', '["https://example.com/"]'];

it('should ignore entries where the address is not a string, array, or null', () => {
it('should ignore entries where the address is not a string', () => {
for (const invalid of invalidAddressStrings) {
expectSpecifierMap(
`{
"foo": ${invalid},
"bar": ["https://example.com/"]
"bar": "https://example.com/"
}`,
'https://base.example/',
{
bar: [expect.toMatchURL('https://example.com/')]
bar: expect.toMatchURL('https://example.com/')
},
[
`Invalid address ${invalid} for the specifier key "foo". ` +
`Addresses must be strings, arrays, or null.`
]
[`Invalid address ${invalid} for the specifier key "foo". Addresses must be strings.`]
);
}
});

it('should ignore entries where the specifier key is an empty string', () => {
expectSpecifierMap(
`{
"": ["https://example.com/"]
"": "https://example.com/"
}`,
'https://base.example/',
{},
[`Invalid empty string specifier key.`]
);
});

it('should ignore members of an address array that are not strings', () => {
for (const invalid of invalidInsideArrayStrings) {
expectSpecifierMap(
`{
"foo": ["https://example.com/", ${invalid}],
"bar": ["https://example.com/"]
}`,
'https://base.example/',
{
foo: [expect.toMatchURL('https://example.com/')],
bar: [expect.toMatchURL('https://example.com/')]
},
[
`Invalid address ${invalid} inside the address array for the specifier key "foo". ` +
`Address arrays must only contain strings.`
]
);
}
});

it('should throw if a scope\'s value is not an object', () => {
for (const invalid of nonObjectStrings) {
expectBad(`{ "scopes": { "https://scope.example/": ${invalid} } }`, 'https://base.example/');
Expand All @@ -120,20 +96,4 @@ describe('Normalization', () => {
expect(parseFromString(`{ "imports": {} }`, 'https://base.example/'))
.toEqual({ imports: {}, scopes: {} });
});

it('should normalize addresses to arrays', () => {
expectSpecifierMap(
`{
"foo": "https://example.com/1",
"bar": ["https://example.com/2"],
"baz": null
}`,
'https://base.example/',
{
foo: [expect.toMatchURL('https://example.com/1')],
bar: [expect.toMatchURL('https://example.com/2')],
baz: []
}
);
});
});
Loading

0 comments on commit 6f99d47

Please sign in to comment.