Open ctcpip opened 3 years ago
in my index.html
I do not have hardcoded script/src tags. Due to unique requirements of our application, the src
path is modified dynamically at runtime. So I think it is failing in this bit of code that is parsing the file and looking for a <script>
with a src
attribute. Which it will never find.
What do your <script>
tags look like in index.html?
Can you share any customizations of outputPaths
or fingerprint
options in ember-cli-build.js
?
Do you have a customized rootURL
in config/environment.js
?
Please run a build with the environment variable DEBUG=ember-auto-import:*
and share the output.
I replied above before seeing your second comment, yes, that must be the issue. Can you say more about why you need to do it that way? Maybe there's an easier alternative that would work with ember-auto-import, or maybe we need to make a more manual way for you to indicate where you want the auto-imported content to go.
"<script src=\"" + foo.bar + "{{rootURL}}assets/vendor.js\" charset=\"utf-8\"><\/script>"
without getting into too many gory details, the client needs to talk to the server to determine the root path due to requirements of an appliance in a datacenter with a reverse proxy...
The challenge here is that we can produce some arbitrary number of bundles, depending on how many places your app and addons might say await import()
. All of those bundles need this URL adjustment.
Some of those bundles will be entrypoint bundles and end up in script tags in index.html, and that is where this failure is coming from. We used to append the entrypoint bundles to vendor.js, but in 2.0 they became standalone files, which is better for caching and makes the build simpler.
And other bundles are lazy and will load only on demand, so you would also need to make sure webpack's runtime loader can find those. The way to do that is to set the __webpack_public_path__
global variable. Even on ember-auto-import 1.x, you need to be careful because if anybody (including your addons) uses await import()
you'll get a lazy bundle that won't load correctly in production if you don't set that.
I think the most robust way to solve your problem is to let the build run in the normal way, and then postprocess the HTML. That way, you will be able to detect all assets that are located under rootURL and rewrite them to be under your dynamically discovered rootURL. For example, the build could produce:
<script src="/assets/vendor.js"></script>
<script src="/assets/chunk-14532812.js"></script>
<link rel="stylesheet" href="/assets/app.css"></script>
And your postprocessor would look at all <script>
(and <link>
, etc) tags and for any that have root-relative URLs, rewrite to something customized so that they can be handled by little bit of custom runtime loader code:
<my-custom-script src="/assets/vendor.js"></script>
<my-custom-script src="/assets/chunk-14532812.js"></script>
<script>
(async function() {
let rootURL = await discoverRootURL();
__webpack_public_path__ = rootURL;
document.querySelectorAll('my-custom-script').forEach(scriptTag => {
let newTag = document.createElement('script');
newTag.src = rootURL + scriptTag.src;
document.body.appendChild(newTag);
})
})()
</script>
The benefit of this strategy is that you can avoid making any assumptions about what the exact assets will be. Your postprocessor reads the HTML and finds all of them no matter how they were produced.
Oh, one thing I just remembered is that ember-auto-import already has some code that sets webpack_public_path, so we will likely need to make a change to avoid a collision when the app itself wants to take over that value.
allllright, here's what I ended up doing:
first, I created an in-repo-addon for the postprocessing (ember g in-repo-addon addon-name
):
'use strict';
module.exports = {
name: require('./package').name, // eslint-disable-line global-require
async postBuild(results) {
const fs = this.project.require('fs-extra');
const jsdom = this.project.require('jsdom');
const file = `${results.directory}/index.html`;
const { JSDOM } = jsdom;
const dom = await JSDOM.fromFile(file);
const { document } = dom.window;
document.querySelectorAll('script[src^="assets"],link').forEach(e => {
const isLink = e.nodeName === 'LINK';
const node = document.createElement(isLink ? 'clever-link' : 'clever-script');
for (const a of e.attributes) {
node.setAttribute(a.name, a.value);
}
if (isLink) {
document.head.appendChild(node);
}
else {
document.body.appendChild(node);
}
e.remove();
});
fs.writeFileSync(file, dom.serialize());
},
isDevelopingAddon() {
return false;
}
};
<script>
function queueResource(nodes, i) {
const e = nodes[i];
const isLink = e.nodeName === 'CLEVER-LINK';
const node = document.createElement(isLink ? 'link' : 'script');
for (const a of e.attributes) {
node.setAttribute(a.name, ['href', 'src'].includes(a.name) ? myDynamicRootURL + a.value : a.value);
}
if (nodes.length > i + 1) {
if (isLink) {
queueResource(nodes, i + 1);
}
else {
node.onload = function() {
queueResource(nodes, i + 1);
};
}
}
if (isLink) {
document.head.appendChild(node);
}
else {
document.body.appendChild(node);
}
e.remove();
}
window.addEventListener('DOMContentLoaded', () => {
__webpack_public_path__ = myDynamicRootURL; // eslint-disable-line camelcase, no-undef
const nodes = document.querySelectorAll('clever-link,clever-script');
queueResource(nodes, 0);
});
</script>
the tricky bit with this was the recursion. otherwise, loading scripts dynamically like this, they are not done being loaded/evaluated before the next script is loaded. so you get errors like define is undefined
. hence the need to call queueResource
within the onload
event handler for scripts.
webpack_public_path
ember-auto-import
v1 - still need to upgrade to v2 again and see how it goes~ I upgraded to v2 and it's working, with all ember tests passingthanks for your help @ef4 -- and code review of the above is more than welcome 😁
I can't compile after upgrading to v2. Build is failing with: