aurelia / webpack-plugin

A plugin for webpack that enables bundling Aurelia applications.
MIT License
90 stars 36 forks source link

Failed loading required CSS file in Aurelia v1 Webpack, but only from certain directories, using SCSS, seems to be a runtime error, or an issue with the plugin. #224

Open Gryhyphen opened 10 months ago

Gryhyphen commented 10 months ago

I'm submitting a bug report

Please tell us about your environment:

Current behavior:

I'm changing over our Aurelia 1 project from jspm to webpack, the goal is to eventually upgrade to aurelia 2. Ran into an issue where at runtime it spits out Error: Failed loading required CSS file: AdminTheme/Styles/layout.scss.

This file is loaded in two locations through the syntax in html templates:

In src/app/app.html it is loaded like so :

    <require from="/src/AdminTheme/Styles/layout.scss"></require>

in src/app/components/ApiRequestGrid/api-request-grid.html

  <require from="../../../AdminTheme/Styles/layout.scss" as="scoped"></require>

It loads correctly in src/app/app.html, I can see the styles being applied and no error is being logged at runtime. When I try to navigate to a page with the component src/app/components/ApiRequestGrid/api-request-grid.html on it, I get the error:

Error: Failed loading required CSS file: AdminTheme/Styles/layout.scss.

I ran webpack stats and found these lines:

         "reasons": [
                {
                    "active": true,
                    "explanation": "",
                    "module": "./src/app/Components/ApiRequestGrid/api-request-grid.html",
                    "moduleId": "app/Components/ApiRequestGrid/api-request-grid.html",
                    "moduleIdentifier": // I'VE OMMITED THIS
                    "moduleName": "./src/app/Components/ApiRequestGrid/api-request-grid.html",
                    "resolvedModule": "./src/app/Components/ApiRequestGrid/api-request-grid.html",
                    "resolvedModuleId": "app/Components/ApiRequestGrid/api-request-grid.html",
                    "resolvedModuleIdentifier":  // I'VE OMMITED THIS
                    "type": "IncludeDependency",
                    "userRequest": "../../../AdminTheme/Styles/layout.scss"
                },
                {
                    "active": true,
                    "explanation": "",
                    "module": "./src/app/app.html",
                    "moduleId": "app/app.html",
                    "moduleIdentifier":// I've OMITTED THIS
                    "moduleName": "./src/app/app.html",
                    "resolvedModule": "./src/app/app.html",
                    "resolvedModuleId": "app/app.html",
                    "resolvedModuleIdentifier": // I'VE OMMMITTED THIS
                    "type": "IncludeDependency",
                    "userRequest": "/src/AdminTheme/Styles/layout.scss"
                }
            ],

Which would seem to imply that webpack is bunding it / finding it / able to resolve the path. At least, seeing this reason array contains 2 elements, and both are the only places in the codebase that are requiring this code, it seems reasonable for me to assume that webpack is doing it's job, and this is an error with either the plugin or runtime.

Expected/desired behavior:

I want to know why my styles aren't loading. Previously, it worked in jspm land because we didn't bundle things properly, the file actually existed at the path on the server that we are hosting.

My understanding is that webpack should be finding the moduleId and cause aurelia likes to load things at runtime, that's why I needed to moduleId everything.

What I don't understand is why does the import in app.html work, but the import in api-request-grid.html does not?

At least, surface better error messages cause I'm at the point where I'm going to need to debug this through brute force and just tracing calls through aurelia and it's not fun.

Allowing people to load scss files from within components to make use of features like the shadow dom. Maybe stuff with css modules / anything to do with scoped css. Giving me reasons to love aurelia. Understanding the pain points people have using aurelia and how you can improve your tooling to make it easier for people to configure and use it.

bigopon commented 10 months ago

One thing that immediately popped when i glanced at this is it could be the layout feature that is causing such issue. Will investigate.

Gryhyphen commented 9 months ago

Unlikely I think. It's affecting all scss files.

It seems to prefer module paths over relative paths. Rather uh, when relativeToFile runs, it seems to extract "app" from the file (variable), and replace all the ../../../ with it

image

resulting in app/admintheme/styles/pages.scss instead of src/admintheme/style/pages.scss

This doesn't happen for module paths (relative to the package root, so like /src/app, or something, not really sure why this works actually) because it doesn't try to do this replacement.

If my file (the variable named file in the image I attached) was src/app/components/ApiRequestGrid/api-request-grid.html instead of app/components/ApiRequestGrid/api-request-grid.html I think this would work.

I have no idea why it is chopping off src from the path/file. Are you able to advise on this?

Obviously, the way it's determining relative to file is very strange, if it actually made a call out to the file directory it would find the file at the location, but this weird "replace ../../etc with the first part of the path of the file" is seemingly wrong.

Gryhyphen commented 9 months ago

My aurelia-path version is 1.1.7, which seems to be the latest (at least according to au new).

So my assumption is that this is a bug. This function shouldn't rely on the root of the file to figure out what the required file is relative to as they may not share a common root.

alternatively, could try figuring out why file is app/ and not src/app, I've spent about 2 days dedicated to tracing calls in aurelia and I still have no idea how it determines to build the file path. I know main is loaded, and then "loadAllModules" is called, but no idea why the string is app/. That being said I still think that there is a fundamental problem with the assumption that the name being requested shares the root of the provided file.

Gryhyphen commented 9 months ago

Yeah so I've verified that that I can resolve the issue by moving the /admintheme directory into the src/app directory.

This solution isn't great, as it creates a large diff. And this codebase uses 'module' paths and relative paths inconsistently so if I move the files something might break somewhere else. Could try duplicating or using a symlink, but aurelia might not be able to handle that.

Aliases in webpack don't seem to help this scenario as it has to do with the logic of relativeToFile being borked. afaik.

I'm looking at maybe seeing if I can just construct manual aliases in the entry point (aurelia.use), going into the loader.moduleRegistry and pushing in data that matches the scssModulePlugin!/adminTheme/styles/<MYSCSSFILE>.scss. But I'm not looking forward to this hack and figuring out how to construct these objects properly.

If that would even fix my issue.

I tried using globalName to import the files using both paths, and neither worked, both had null "css" when it passed through the text loader thing. So I still don't quite understand the nature of why some imports work, and others do not. Cause this path /src/admintheme/style/<MYSCSS>.scss when required in a template works, but the same path when I use it in globalName does not.

I suspect that's because something, somewhere, is adding additional context about scss files that the code doesn't flow down normally. Like I think it tries to add both the file and the scssModulePlugin! maybe?

Does any of this make sense?

Gryhyphen commented 9 months ago

@bigopon

I modified aurelia-path with a hack just to verify that changing the relativeToPath function would resolve my issues, and can confirm that it appears like there is a bug in the function as this resolves my issue and allows the css to load:

function relativeToFile(name, file) {
    var fileParts = file && file.split('/');
    var nameParts = name.trim().split('/');
    if (nameParts[0].charAt(0) === '.' && fileParts) {
        var normalizedBaseParts = fileParts.slice(0, fileParts.length - 1);
        nameParts.unshift.apply(nameParts, normalizedBaseParts);
    }
    trimDots(nameParts);

    // return nameParts.join('/');
    // I've modified the above line with this below code
    // TODO: REMOVE THIS HACK

    let result = nameParts.join('/');
    if (/^AdminTheme\//.test(result)) {
        result = result.replace('AdminTheme', '/src/AdminTheme');
    }
    return result;
}

I might find a way just to wrap this function to resolve my issue for now (can't directly modify aurelia modules in production). Luckily, I don't have too many files outside of /app that we import, but it is a hack.

Gryhyphen commented 9 months ago

Still dealing with this issue. I have written a monkey patch on the loader loadText method to progress for now while I'm waiting for this to be fixed or for me to get better information on how this needs to be configured to not be an issue.

main.ts

export function configure(aurelia : Aurelia) {
    aurelia.use
        .standardConfiguration()
        .developmentLogging()
        .plugin(PLATFORM.moduleName("aurelia-after-attached-plugin"))
        .plugin(PLATFORM.moduleName("aurelia-kendoui-bridge"))

    const originalLoadText = aurelia.loader.loadText.bind(aurelia.loader);
    /**
     * A function that monkey patches the `loadText` method of the Aurelia loader to replace a URL with a modified version.
     * 
     * Please note, this creates duplicates of the loaded modules as it loads the css twice under different moduleIds.
     * for the different
     * @param url - The URL to load.
     * @returns A Promise that resolves to the loaded text.
     * @todo - This is a temporary solution. Remove this when the issue is fixed. See https://github.com/aurelia/webpack-plugin/issues/224#issuecomment-1786559006
     */
    async function monkeyPatchLoadText (url: string): Promise<string> {
        if (/^AdminTheme\//.test(url)) {
            url = url.replace('AdminTheme', '/src/AdminTheme');
        }
        return await originalLoadText(url);
    }
    // Apply monkey patch
    aurelia.loader.loadText = monkeyPatchLoadText;

    aurelia.start().then(() => aurelia.setRoot(PLATFORM.moduleName('app/app')));

}

A limitation of this solution is that it results in duplicate modules being loaded. I have observed that when I modify relativeToFile that I do not get duplicates, but baring forking or using a tool like package-patch, I'm not sure how to apply a monkey patch to a js module's exports, I've tried and been unsuccessful.

So, the issue I'm experiencing is definitely to do with the webpack-loader, which just so happens to depend on aurelia-path, which has a borked relativeToFile method afaik.

Would definitely like an update on this and any advice on a better method of getting aurelia to handle this.

What do I mean by duplicate modules?

Here is a rough look at what my moduleRegistry looks like using the loadText monkey patch (forgive weird formatting)

/src/AdminTheme/Styles/elements.scss:
/src/AdminTheme/Styles/layout. scss:
/src/AdminTheme/Styles/pages.scss:
scss-resource-plugin!/src/AdminTheme/Styles/elements.scss: {/src/AdminTheme/—
scss-resource-plugin!/src/AdminTheme/Styles/layout.scss: {/src/AdminTheme/St—
scss-resource-plugin!/src/AdminTheme/Styles/login.scss:
scss-resource-plugin!/src/AdminTheme/Styles/pages.scss:
scss-resource-plugin!AdminTheme/Styles/elements. scss: {AdminTheme/Styles/el
scss-resource-plugin!AdminTheme/Styles/layout. scss: {AdminTheme/Styles/layo
scss-resource-plugin!AdminTheme/Styles/pages. scss: {AdminTheme/Styles/pages

Here is what it looks like when I modify relativeToFiles in aurelia-path

/src/AdminTheme/Styles/elements.scss:
/src/AdminTheme/Styles/layout. scss:
/src/AdminTheme/Styles/pages.scss:
scss-resource-plugin!/src/AdminTheme/Styles/elements.scss: {/src/AdminTheme/—
scss-resource-plugin!/src/AdminTheme/Styles/layout.scss: {/src/AdminTheme/St—
scss-resource-plugin!/src/AdminTheme/Styles/login.scss:
scss-resource-plugin!/src/AdminTheme/Styles/pages.scss:

And no, I don't know why it's loaded as whatever /src/admintheme/styles/elements.scss is and the one prefixed with the scss-resource-plugin. That's another duplication that I'm just chalking up to aurelia being aurelia. (I know WHY scss-resource-plugin! exists (so the scss loader thingy can run), but not why the non-scss-resource-plugin version exists).

Still, less duplication, so I would like to see an official fix here, because it is non-trivial for me to modify relativeToFile as it is in an npm module in such a way that severally limits my options to patch it.