0

I have a Play application. The UI of the application is in Angular. I have created a folder ui which is the top level Angular directory.

build.sbt
`ui-dev-build` := {
    implicit val UIroot = baseDirectory.value / "ui"
      if (runDevBuild != Success) throw new Exception("Oops! UI Build crashed.")
}

def runDevBuild(implicit dir: File): Int = ifUiInstalled(runScript("npm run build"))

package.json
"build": "ng build --output-path ../public/ui",

When the Angular application is build, I transfer the output to the public folder of the Play framework. From there, Play transfers the contacts of the public folder to target folder for deployment. In the index.html (homepage html file), I access angular by including the scripts created in Angular build.

<script src="@routes.Assets.versioned("ui/runtime.js")" type="text/javascript"></script>
    <script src="@routes.Assets.versioned("ui/vendor.js")" type="text/javascript"></script>
    <script src="@routes.Assets.versioned("ui/styles.js")" type="text/javascript"></script>
    <script src="@routes.Assets.versioned("ui/main.js")" type="text/javascript"></script>
<script src="@routes.Assets.versioned("ui/scripts.js")" type="text/javascript"></script>

This works fine.

I want to use ng-ace2-editor in my application - https://github.com/fxmontigny/ng2-ace-editor. I have added it in package.json - "ng2-ace-editor": "0.3.9" and I can see that ng2-ace-editor directory is present in node_modules.

When I run the application, I get error

GET http://localhost:9000/mode-html.js net::ERR_ABORTED 404 (Not Found)
exports.loadScript  @   index.js:3802
exports.loadModule  @   index.js:4174
setMode @   index.js:10152
push../node_modules/ng2-ace-editor/src/component.js.AceEditorComponent.setMode

I can't understand how to make my application find mode-html.js. The file is present at location "./node_modules/ace-builds/src-min/mode-html.js. I have added this path in "script":[] of package.json but I still get the error.

"scripts":[
              "./node_modules/ace-builds/src-min/ace.js",
              "./node_modules/ace-builds/src-min/theme-eclipse.js",
              "./node_modules/ace-builds/src-min/mode-html.js"
]

Interestingly, things work if I include ace.js in the homepage file

<script src="@routes.Assets.versioned("ui/runtime.js")" type="text/javascript"></script>
    <script src="@routes.Assets.versioned("ui/vendor.js")" type="text/javascript"></script>
    <script src="@routes.Assets.versioned("ui/styles.js")" type="text/javascript"></script>
    <script src="@routes.Assets.versioned("ui/main.js")" type="text/javascript"></script>
<script src="@routes.Assets.versioned("ui/scripts.js")" type="text/javascript"></script>
    <script src="@routes.Assets.versioned("javascripts/common/vendor/ace/src-min/ace.js")" type="text/javascript"></script> <!-- this makes things work-->

So I know that issue is that my mode-html.js file is not getting served and most likely it is the path resolution issue but I can't figure out what is it.

Further analysis shows that the following code in ace.js causes the error.

exports.loadModule = function(moduleName, onLoad) {
    var module, moduleType;
    if (Array.isArray(moduleName)) {
        moduleType = moduleName[0];
        moduleName = moduleName[1];
    }

    try {
        module = require(moduleName);
    } catch (e) {}
    if (module && !exports.$loading[moduleName])
        return onLoad && onLoad(module);

    if (!exports.$loading[moduleName])
        exports.$loading[moduleName] = [];

    exports.$loading[moduleName].push(onLoad);

    if (exports.$loading[moduleName].length > 1)
        return;

    var afterLoad = function() {
        require([moduleName], function(module) {
            exports._emit("load.module", {name: moduleName, module: module});
            var listeners = exports.$loading[moduleName];
            exports.$loading[moduleName] = null;
            listeners.forEach(function(onLoad) {
                onLoad && onLoad(module);
            });
        });
    };

    if (!exports.get("packaged"))
        return afterLoad();

    net.loadScript(exports.moduleUrl(moduleName, moduleType), afterLoad);
    reportErrorIfPathIsNotConfigured();
};

var reportErrorIfPathIsNotConfigured = function() {
    if (
        !options.basePath && !options.workerPath 
        && !options.modePath && !options.themePath
        && !Object.keys(options.$moduleUrls).length
    ) {
        console.error(
            "Unable to infer path to ace from script src,",
            "use ace.config.set('basePath', 'path') to enable dynamic loading of modes and themes",
            "or with webpack use ace/webpack-resolver"
        );
        reportErrorIfPathIsNotConfigured = function() {};
    }
};

Why does explicitly calling <script src="@routes.Assets.versioned("javascripts/common/vendor/ace/src-min/ace.js")" type="text/javascript"></script> make the code work. Should this script be already available in ui/scripts.js as per Angular packaging method https://upgradetoangular.com/angular-news/the-angular-cli-is-a-great-way-to-build-your-angular-app-but-what-it-does-can-be-a-mystery-what-are-those-files-it-generates/?

Manu Chadha
  • 15,555
  • 19
  • 91
  • 184

1 Answers1

0

I finally was able to make my code work. My setup is different. I build my Angular application and the it is served from my Play server. The angular build is stored in Play's /public/ui folder. The requests should be in format /assets/ui/.. which gets mapped to /public/ui/... due to a rule in Play routes file

GET     /assets/*file               controllers.Assets.versioned(path="/public", file: Asset)

When I ran the code, I got error.

Uncaught DOMException: Failed to execute 'importScripts' on 'WorkerGlobalScope': The script at 'http://localhost:9000/worker-javascript.js' failed to load.
    at blob:http://localhost:9000/3df21e42-fecb-4026-8bd6-f2b0d1d0540a:1:1

Earlier, I also got error Unable to infer path to ace from script src, use ace.config.set('basePath', 'path') to enable dynamic loading of modes and themes or with webpack use ace/webpack-resolver

It seems ng-ace-editor imports .js scripts (theme, mode, worker) based on the theme and mode of the editor. The theme and mode js files can be included in scripts.js but some worker-.js files can't be (I don't know why, maybe because worker ones are loaded dynamically using importScript.

The scripts section in Angular.json is (this will all get bundled in scripts.js in Angular's final bundle)

"scripts": [
              "./node_modules/ace-builds/src/ace.js",
              "./node_modules/ace-builds/src/theme-eclipse.js",
              "./node_modules/ace-builds/src/theme-monokai.js",
              "./node_modules/ace-builds/src/mode-html.js"
            ]]

To include worker-.js files, I added this rule because it seems angular-cli can't load from node_modules. So I had to copy the files from node modules to root of my ui build - How to include assets from node_modules in angular cli project

"assets": [
              "src/assets",
              "src/favicon.ico",
              {
                "glob": "**/*",
                "input": "./node_modules/ace-builds/src/",
                "output": "/"
              }
            ],

When I executed the code, I found error that http://localhost:9000/worker-javascript.js can't be loaded. I realised that my files are loaded in /assets/ui/ path and not in the server's root directory. So I set the basepath to /assets/ui in the component's .ts file

import * as ace from 'ace-builds/src-noconflict/ace';
    ace.config.set('basePath', '/assets/ui/');
    ace.config.set('modePath', '');
    ace.config.set('themePath', '');

In summary basePath seem to be what is used to load scripts dynamically (eg worker scripts) modePath and themePath are / as the mode and theme scripts are bundled in scripts.js and are available at root level need to copy worker-.js files outside node_modules as angular_cli can't copy assets from node_modules

Manu Chadha
  • 15,555
  • 19
  • 91
  • 184