-
Notifications
You must be signed in to change notification settings - Fork 201
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New AMD-style module loader. #254
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(just talked offline) It sounds like this needs to work in at least one place that doesn't have URL
(IE11), but here's a few things that it could be useful for anyway.
packages/amd-loader/amd-loader.js
Outdated
Module.prototype.resolveUrl = function(path) { | ||
if (path.indexOf('://') !== -1) { | ||
// Already a fully qualified URL. | ||
// TODO(aomarks) Is this a good enough check? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you have URL
, then I think you can try-catch new URL(path)
, which throws if it's not given a full URL, IIRC.
packages/amd-loader/amd-loader.js
Outdated
return path; | ||
} | ||
// Just let the browser do path resolution/normalization. | ||
Module.resolverAnchor.href = this.basePath + path; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, if you have URL
, (new URL(path, this.basePath)).href
is safer since it handles joining them properly:
> "http://example.com/page" + "some/path.js"
< "http://example.com/pagesome/path.js"
... when really it should be:
> (new URL("some/path.js", "http://example.com/page")).href
< "http://example.com/some/path.js"
(this.basePath
needs to be a full URL though.)
packages/amd-loader/amd-loader.js
Outdated
return url + '/'; | ||
} | ||
// http://example.com/foo/bar.html -> http://example.com/foo/ | ||
return url.substring(0, lastSlash + 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, if you have URL
: const getBasePath = url => new URL("./", url).href;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth noting that IE 11 does not have URL, but the webcomponents polyfill includes it.
packages/amd-loader/README.md
Outdated
|
||
# @polymer/amd-loader | ||
|
||
A JavaScript library that loads AMD-style modules. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add some text saying that it loads path style modules and not name style modules, and that it's intended to mimic the semantics of es6 modules
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, let's call out that it's a subset and mention the differences you have in the PR description.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just pushed a much more complete README, PTAL.
packages/amd-loader/amd-loader.js
Outdated
(function() { | ||
|
||
function Registry() { | ||
this.modules = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use Object.create(null)
for a map-style object, otherwise stuff like this.modules['valueOf']
will be truthy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
packages/amd-loader/amd-loader.js
Outdated
this.modules = {}; | ||
this.topLevelIdx = 0; | ||
this.previousTopLevelUrl = undefined; | ||
this.pendingDefine = undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document these fields please. Maybe use // @ts-check
and jsdoc to get basic type checking, or use full typescript and tsc can also handle transpiling to ES5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 on TypeScript
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Converted to TypeScript, added tslint, and added a fair bit of documentation. PTAL!
packages/amd-loader/amd-loader.js
Outdated
function getBasePath(url) { | ||
// TODO(aomarks) Maybe a better way to do this. | ||
var lastSlash = url.lastIndexOf('/'); | ||
if (lastSlash === url.indexOf('://') + 2) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about urls like //example.com
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be handled now. All URLs are now normalized using the <a>
before we compute the base path, which should always add an explicit scheme. Tested it in Chrome/Firefox/Safari/IE11 to be sure.
packages/amd-loader/amd-loader.js
Outdated
return url + '/'; | ||
} | ||
// http://example.com/foo/bar.html -> http://example.com/foo/ | ||
return url.substring(0, lastSlash + 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth noting that IE 11 does not have URL, but the webcomponents polyfill includes it.
packages/amd-loader/amd-loader.js
Outdated
this.pendingDefine = undefined; | ||
} | ||
|
||
Registry.prototype.get = function(url) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document these methods as well please. For most of them, just the types of the params and return is enough, but for ones with complicated invariants they're maintaining those would be valuable as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
A few high-level things:
|
Seems like with some unit tests we can get by with a built-in function that just has to find a base path from a URL, no? The URL polyfill is bigger than this entire library, and in theory this loader could be useful to people who don't otherwise need any webcomponents polyfills. |
packages/amd-loader/README.md
Outdated
|
||
# @polymer/amd-loader | ||
|
||
A JavaScript library that loads AMD-style modules. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, let's call out that it's a subset and mention the differences you have in the PR description.
packages/amd-loader/amd-loader.js
Outdated
|
||
(function() { | ||
|
||
function Registry() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does Registry
need to be a "class" at all? If it's a singleton you can just put the state and functions in the module closure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I thought the class might be easier to read, but this seems simpler. Incidentally it helps with minimization, since neither babel-minify nor uglify-js seem to want to rename prototype properties.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also got rid of the Module
class, from offline discussion. Size is now down to 0.7KB :)
packages/amd-loader/amd-loader.js
Outdated
this.modules = {}; | ||
this.topLevelIdx = 0; | ||
this.previousTopLevelUrl = undefined; | ||
this.pendingDefine = undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 on TypeScript
packages/amd-loader/amd-loader.js
Outdated
|
||
(function() { | ||
|
||
function Registry() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even though what's inside the module closure is top-level, I'd still indent it. Initially I thought some things below were outside of the IIFE for some reason.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just what clang-format does. I think I prefer it?
packages/amd-loader/amd-loader.js
Outdated
} | ||
} | ||
|
||
for (var i = 0; i < (deps || []).length; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(deps || []).length
will be evaluated every iteration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
packages/amd-loader/amd-loader.js
Outdated
} else if (depSpec === 'require') { | ||
factoryArgs[i] = this.makeDynamicImporter(); | ||
|
||
} else if (depSpec === 'meta') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AMD includes a "module" built-in dependency, but the spec doesn't seem to specify it that I can see. In IMD it has the exports
and id
properties. Maybe we dug that out of RequireJS source. Seems like in this loader module.id
might === import.meta.url
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the AMD spec defers to the CommonJS spec for module
, which says:
The "module" object must have a "id" property that is the top-level "id" of the module. The "id" property must be such that require(module.id) will return the exports object from which the module.id originated. (That is to say module.id can be passed to another module, and requiring that must return the original module). When feasible this property should be read-only, don't delete.
The "module" object may have a "uri" String that is the fully-qualified URI to the resource from which the module was created. The "uri" property must not exist in a sandbox.
I'm open to using module.id
or module.uri
(or both). Although, I do also like the idea of providing it via meta
, because we are trying to match the semantics of import.meta
. I don't know if import.meta.scriptElement
is going to happen, but if it did, then we would have a natural place to put it. I think this discussion depends on what our position is on interopability with other AMD modules/loaders.
packages/amd-loader/amd-loader.js
Outdated
}; | ||
|
||
script.onerror = function() { | ||
throw new Error('error loading module', url); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this need to propagate to dependents?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, and this now happens.
packages/amd-loader/amd-loader.js
Outdated
|
||
function getBasePath(url) { | ||
// TODO(aomarks) Maybe a better way to do this. | ||
var lastSlash = url.lastIndexOf('/'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might have issues with slashes in query parameters or fragments. They are possible in module specifiers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added handling for those cases. Will cover in unit tests.
packages/amd-loader/package.json
Outdated
@@ -0,0 +1,16 @@ | |||
{ | |||
"name": "@polymer/amd-loader", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might want a name that indicates that this isn't full AMD: amd-path-loader
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Converted to TypeScript, addressed some comments (still some pending), and did a little refactoring (which incidentally dropped the size a bit too). Also added minified output and tslint.
packages/amd-loader/amd-loader.js
Outdated
|
||
(function() { | ||
|
||
function Registry() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just what clang-format does. I think I prefer it?
packages/amd-loader/amd-loader.js
Outdated
|
||
(function() { | ||
|
||
function Registry() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I thought the class might be easier to read, but this seems simpler. Incidentally it helps with minimization, since neither babel-minify nor uglify-js seem to want to rename prototype properties.
packages/amd-loader/amd-loader.js
Outdated
(function() { | ||
|
||
function Registry() { | ||
this.modules = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
packages/amd-loader/amd-loader.js
Outdated
this.modules = {}; | ||
this.topLevelIdx = 0; | ||
this.previousTopLevelUrl = undefined; | ||
this.pendingDefine = undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Converted to TypeScript, added tslint, and added a fair bit of documentation. PTAL!
packages/amd-loader/amd-loader.js
Outdated
this.pendingDefine = undefined; | ||
} | ||
|
||
Registry.prototype.get = function(url) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
packages/amd-loader/amd-loader.js
Outdated
function getBasePath(url) { | ||
// TODO(aomarks) Maybe a better way to do this. | ||
var lastSlash = url.lastIndexOf('/'); | ||
if (lastSlash === url.indexOf('://') + 2) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be handled now. All URLs are now normalized using the <a>
before we compute the base path, which should always add an explicit scheme. Tested it in Chrome/Firefox/Safari/IE11 to be sure.
packages/amd-loader/amd-loader.js
Outdated
} | ||
} | ||
|
||
for (var i = 0; i < (deps || []).length; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
packages/amd-loader/amd-loader.js
Outdated
|
||
function getBasePath(url) { | ||
// TODO(aomarks) Maybe a better way to do this. | ||
var lastSlash = url.lastIndexOf('/'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added handling for those cases. Will cover in unit tests.
packages/amd-loader/amd-loader.js
Outdated
} else if (depSpec === 'require') { | ||
factoryArgs[i] = this.makeDynamicImporter(); | ||
|
||
} else if (depSpec === 'meta') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the AMD spec defers to the CommonJS spec for module
, which says:
The "module" object must have a "id" property that is the top-level "id" of the module. The "id" property must be such that require(module.id) will return the exports object from which the module.id originated. (That is to say module.id can be passed to another module, and requiring that must return the original module). When feasible this property should be read-only, don't delete.
The "module" object may have a "uri" String that is the fully-qualified URI to the resource from which the module was created. The "uri" property must not exist in a sandbox.
I'm open to using module.id
or module.uri
(or both). Although, I do also like the idea of providing it via meta
, because we are trying to match the semantics of import.meta
. I don't know if import.meta.scriptElement
is going to happen, but if it did, then we would have a natural place to put it. I think this discussion depends on what our position is on interopability with other AMD modules/loaders.
factory.apply(null, args); | ||
} | ||
mod.resolved = true; | ||
for (const callback of mod.notify) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be careful with for/of loops if we're trying to be small here, or at least explicitly set downlevelIteration
to false in tsconfig.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added downlevelIteration: false
.
* that Module objects are created and registered before they are loaded, | ||
* which is why this is not simply part of construction. | ||
*/ | ||
define(deps: string[], factory?: (...args: {}[]) => void) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does Babel never output factories that return a value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We think not.
if (this.needsLoad === false) { | ||
return; | ||
} | ||
this.needsLoad = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
const mod = this; | ||
script.onload = function() { | ||
if (pendingDefine !== undefined) { | ||
pendingDefine(mod); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you could just create the module object in define()
and stash it in a global, and then you know if define was called. If it wasn't, then create one. This should let you get rid of pendingDefine
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed offline. The timing doesn't work out -- the module object has already been created by the parent module at this time (whichever one first declared a dependency on this module), and here we are actually waiting to find out our URL so that we can associate that existing module object with the dependencies and factory that we now have.
args[i] = this.exports; | ||
|
||
} else if (depSpec === 'require') { | ||
args[i] = this.require.bind(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this right? I think the require
that's injected accepts a single string argument? This might need to be:
args[i] = (dep) => this.require([dep]);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, dynamic require takes an array of dependencies.
} else if (depSpec === 'require') { | ||
args[i] = this.require.bind(this); | ||
|
||
} else if (depSpec === 'meta') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd drop this for now pending a change to the import.meta transform.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Going with meta for now.
* it doesn't exist yet. | ||
*/ | ||
function getModule(url: string): Module { | ||
let mod = registry[url]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to be really safe against someone using a module name like __proto__
, add a prefix to the url.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We know that the keys are all fully qualified URLs (now with a branded type to be really clear), so I don't think this is a problem.
private resolved = false; | ||
|
||
/** Callbacks from dependents waiting for this module to resolve. */ | ||
private notify: Array<() => void> = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than an array of callbacks, keep an array of dependent modules, and inline onDepResolved
into the loop where the callbacks are called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed offline. The current factoring exploits the fact that the same implementation of require
can be used both for module initialization ("define"), and as the function we pass to modules requesting a dynamic "require". However, in the latter case, we don't have any dependents to notify, so that happens only in "define" with an additional closure.
packages/amd-loader/.gitignore
Outdated
@@ -0,0 +1,2 @@ | |||
/node_modules/ | |||
/lib/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're going to have a .gitignore, you're going to need a .npmignore because, if memory serves, npm publish honors a .gitignore if no .npmignore is present, because the world would be too predictable and reasonable otherwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. Empty .npmignore will do
} | ||
} | ||
|
||
for (let i = 0; i < deps.length; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this comment is golfy, but is there a reason not to do:
for (const depSpec of deps) {
...
args.push(...)
...
}
instead of args[i] = ...
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
}; | ||
|
||
script.onerror = () => { | ||
throw new Error('error loading module ' + mod.url); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this do anything functionally different than if you hadn't added the onerror?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, but now it does because there is propagation.
checkDeps(); | ||
} | ||
|
||
let pendingDefine: ((mod: Module) => void)|undefined = undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is kindof maybe explained inside window.define
's leading comment, but it could probably use a comment of its own to better annotate its role in the process and/or why it is a global singleton?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I rearranged the window.define function to the top so that it is closer to this variable.
// fired by this <script>, which will be handled by the loading script, | ||
// which will invoke the callback with our module object. | ||
let defined = false; | ||
pendingDefine = (mod) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Talked about this offline, but I was concerned here that this was overwriting a global singleton and that doing so invites, potentially, a tricky-to-diagnose error condition if there is a possibility of two consecutive define()
invocations before the triggering of the pendingDefine()
inside script.onload
.
I just pushed some WCT tests, and some bug fixes that they revealed. PTAL! The major remaining piece here is error handling, e.g. it doesn't yet support invoking the error callback for dynamic require calls when a dependency cannot be resolved. |
packages/amd-loader/.gitignore
Outdated
@@ -0,0 +1,2 @@ | |||
/node_modules/ | |||
/lib/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. Empty .npmignore will do
@@ -0,0 +1,237 @@ | |||
type modCallback = (...args: {}[]) => void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
capitalize
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
return mod; | ||
} | ||
|
||
type normalizedUrl = string&{_normalized: never}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
capitalize and move to top of file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
define: (deps: string[], factory: modCallback) => void; | ||
} | ||
|
||
(function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you shouldn't need an IIFE in the source - either the module setting in tsconfig will create one, or just use a block, which should be compiled to an IIFE.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually as far as I can tell, TypeScript will not generate an IIFE even when the target is es5, so it will leak transpiled let
and const
variables up to function/global scope. (It does at least rename variables to prevent collisions within the compiled program.) There also seems to be no module setting that changes this behavior (anyway, this file is not a module).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default module setting is CommonJS, which might not be obvious in the output here because you have no imports or exports. "module": "none"
and using a block might do it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, module: none and target:es5 with a block will just emit a block with var
s:
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "none"
}
}
test.ts
{
let x = 0;
}
test.js
{
var x = 0;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, that just seems broken... unless the rationale is without a module setting they can't know that this file isn't the whole program?
Ok, then. Thanks for humoring me! 🤷♂️
* - http://example.com/foo/?qu/ery#fr/ag => http://example.com/foo/ | ||
*/ | ||
function getUrlBase(url: normalizedUrl): normalizedUrl { | ||
url = url.split('?')[0] as normalizedUrl; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you use the anchor for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.pathname
could be useful, but then we need to reconstruct scheme, host, etc., so we think this is simpler for now.
Do you have an idea for a new name that better hints at the specific functionality? |
Does seem like we need something, but nothing so far sounds great: amd-path-loader Any other ideas? |
Done. Needed some more stuff in .npmignore anyway. |
amd-path-loader isn't bad, IMO. js-module-loader? |
We decided to go with esm-amd-loader because the key distinguishing feature is that it replicates ES module behavior. |
Disable AMD loader tests on Windows because WCT is broken.
This is a replacement for our use of RequireJS to use as the loader for our ES modules transform. It is AMD-ish, but not 100% compliant with the AMD spec or RequireJS.
Differences from RequireJS:
0.9 KB minified + compressed vs 6.6 KB for RequireJS.
Correctly resolves relative paths above the base URL (see [build/polyserve] AMD transform/requirejs results in multiple requests for same import #211 and possibly [polyserve] Duplicate CE definition errors in Firefox and Edge when combining paths and bare specifiers in tests #203).
Is entirely path-based. Does not allow registering a module by name. The only API is
define(deps, factory)
.define()
module factories are always executed. RequireJS only starts executing a module dependency graph when triggered with arequire()
. The Babel AMD transform only emitsdefine()
calls, so this eliminates the need for us to inject arequire()
call during build.Identifies top-level scripts and ensures they execute in order, like
type=module
scripts do. This would eliminate some complexity in our build pipeline, which does the same thing using code generation, and has to do some ugly tricks to preserve script-order information through theHtmlSplitter
process.Modules can request a
meta
dependency, which is resolved to{url: <moduleUrl>}
. This should be able to simplify our build-timeimport.meta
transform, and has the advantage of not being tied to a particular static path (i.e. you could move the module around your tree post-build and still get a correct URL).Modules always provide an
exports
object to their dependents, even if nothing was exported from the module, which is more similar to native ES modules. This would fix https://github.com/Polymer/polymer-cli/issues/995 in the general case.