Skip to content

Get document URL in esm-amd-loader#407

Merged
aomarks merged 20 commits intoPolymer:masterfrom
LarsDenBakker:feature/resolve-html-import-url
Jun 7, 2018
Merged

Get document URL in esm-amd-loader#407
aomarks merged 20 commits intoPolymer:masterfrom
LarsDenBakker:feature/resolve-html-import-url

Conversation

@LarsDenBakker
Copy link
Copy Markdown
Contributor

@LarsDenBakker LarsDenBakker commented May 23, 2018

Currently the esm-amd-loader assumes that when it finds a top level module script, it's always a script in the index.html. When using HTML Imports (and possibility in the future HTML modules as well) this is not always the case.

Relative module import specified in HTML imports are resolved properly for es modules, but when transpiled to AMD they are always resolved relative to the index.html.

This PR makes it so that relative imports are resolved relative to the document (index or HTML import) that includes the script. It also makes it so that the correct import.meta.url is returned for these AMD modules.

I didn't change the tests yet, I first wanted to see if this was the right direction to fix HTML import - es module interop.

Fixes #391

Copy link
Copy Markdown
Member

@aomarks aomarks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! Looks good overall.

if (defined === false) {
pendingDefine = undefined;
const url = baseUrl + '#' + topLevelScriptIdx++ as NormalizedUrl;
const url = documentUrl + '#' + topLevelScriptIdx++ as NormalizedUrl;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you'll also need to update line 267 (url: (module.isTopLevel === true) ? baseUrl : module.url), because when a module is top-level, we currently just return the const document URL when meta is requested. To do it correctly you'll need to return this new module.url with the artificial # fragment we added stripped off.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I've made the change.

define: ((deps: string[], moduleBody: OnExecutedCallback) => void)&{
_reset?: () => void;
};
HTMLImports: { importForElement: (element: Element) => HTMLLinkElement | undefined }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HTMLImports?: ... since it might not be defined

} else {
// On chrome's native implementation it's not possible to get a direct reference to the link element,
// create a new script and let the browser resolve the url.
const script = currentScript.ownerDocument.createElement('script');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd use an a tag with an href instead of a script, just to be more consistent with what we do with normalizeUrl above.

// On chrome's native implementation it's not possible to get a direct reference to the link element,
// create a new script and let the browser resolve the url.
const script = currentScript.ownerDocument.createElement('script');
script.src = './';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs to be '' instead of './', otherwise you'll get e.g. http://localhost/ instead of http://localhost/import.html.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't think '' would resolve to anything, but it did. It's better because it resolves to the filename.

return baseUrl;
}
// Take the import href and strip off the filename
return htmlImport.href.substring(0, htmlImport.href.lastIndexOf('/') + 1);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to be a bit more careful here, e.g. this will return http:// if href is http://example.com. We handle this above with normalizeUrl (which ensures there is always a trailing slash), and getUrlBase (which handles some more cases).

Copy link
Copy Markdown
Contributor Author

@LarsDenBakker LarsDenBakker May 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if the href will ever be http://example.com for a html import, won't it always need to be a specific file? But it turns out we can just return the href here, this is in line with what we're returning from the native implementation as well.

@LarsDenBakker
Copy link
Copy Markdown
Contributor Author

LarsDenBakker commented May 30, 2018

@aomarks I've made the changes and added tests.

I couldn't get the tests to run from a html import, wct doesn't run suite() and test() created inside an import. I didn't want to introduce too much deviation from the current set up just for html imports. Creating the imports imperatively does the job pretty simply as well and keeps it in the same file. What do you think?

I added the v1 webcomponents loader, as the v2 loader does not add html imports.

@LarsDenBakker
Copy link
Copy Markdown
Contributor Author

The tests run fine for me locally, but I'm not sure what's going wrong inside appveyor and travis?

@aomarks
Copy link
Copy Markdown
Member

aomarks commented Jun 7, 2018

Looks good, just a few small more comments. Sorry for the delay! I'm also working on #441 so that we'll have this library running on Safari and IE11 in continuous integration. Have you tested manually on IE11 btw?

}

function testImport(href: string, expectedOrder: string[], done: () => void) {
// Each time and amd module in the chain is executed, it registers itself.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/and/an

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

// Each time and amd module in the chain is executed, it registers itself.
// If we've reached the length of modules we are expecing to be loaded,
// we check if the right modules were loaded in the expected order
window.addExcutedForImport = (name: string) => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addExecutedForImport

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

"sourceMap": false,
"lib": [
"es5",
"es2015",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need this to be es2015? We need the tests to run in IE11 without adding polyfills so only having es5 helps make sure we aren't using APIs that won't be available.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I didn't mean to change that.


test('import with meta', (done) => {
window.testImportMeta = (url) => {
assert.match(url, /https?:\/\/.+\/html-import\/meta/);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check that it ends exactly in html-import/meta/import-meta.html$

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, made the change.


<script>
define(['meta'], (meta) => {
window.testImportMeta(meta.url);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be a little nicer to have this set a global, and then check it inside testImport before you call done (or something like that) to ensure that the assertion is actually run. Right now if this module didn't execute at all for some reason, the test would still pass.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is already the case. In the start of the test we make a global function which is called from the import. If the module file fails to load or if the code fails to run, the function isn't called and thus done isn't called. This causes the test to fail.

@LarsDenBakker
Copy link
Copy Markdown
Contributor Author

@aomarks Made the changes. Also, I've run the tests on chrome, firefox, safari, edge and IE11.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ES modules inside HTML imports rewritten to AMD do not have the correct import.meta.url set

3 participants