Search code examples
javascriptangularjsace-editor

Ace text editor - "require" fails if the source is minified


I am attempting to use the ace text editor with angular, and one of the needed behaviors requires a call to ace.require.

I've included the appropriate files, but I am seeing that if I minify my javascript source, that doesn't work anymore. This is clearly because the path becomes invalid, but I'm not sure how to handle the situation except to manually load the mentioned files outside of my optimized bundles. I am using ASP.NET MVC Web Optimization Framework to load my javascripts as "bundles" that are compact and minified.

I am using ui-ace directive to load it, but I have tested without any directive and the behavior is the same whether or not I am going through angular.

Has anyone else discovered a workaround to this?

It's pretty simple code ...

Bundle

bundles.Add( new ScriptBundle( "~/bundles/js/ace" )
    // include the ace text editor
    .Include( "~/application/assets/scripts/ace/ace.js" )
    .Include( "~/application/assets/scripts/ace/ext-language_tools.js" )));

Javascript

    if (angular.isDefined(opts.require)) {
        opts.require.forEach(function (n) {
            window.ace.require(n);
        });
    }

This can also just be typed explicitly as ...

ace.require('ace/ext/language_tools');

HTML

<div ng-model="Model.Scripting"
     ui-ace="{
        useWrapMode: true,
        showGutter: true,
        theme: 'chrome',
        mode: 'javascript',
        require: ['ace/ext/language_tools'],
        advanced: {
            enableSnippets: true,
            enableBasicAutocompletion: true,
            enableLiveAutocompletion: true
        }
    }">
</div>

HOW TO REPRODUCE

Since I cannot figure out a way to get the whole thing to you exactly as it needs to be, I've recreated the behavior using nodejs, bower, and gulp.

NODE JS/BOWER

Start by running these commands in nodejs, in a new folder.

npm install bower npm install gulp npm install gulp-concat npm install gulp-rename npm install gulp-uglify npm install run-sequence

bower install angular bower install ace-builds bower install angular-ui-ace

GULP

You can use this gulpfile.js to build the script file.

var
  gulp = require('gulp'),
  concat = require('gulp-concat'),
  rename = require('gulp-rename'),
  uglify = require('gulp-uglify'),
  sequence = require('run-sequence');

gulp.task('default', function() {
  return sequence([
    "ace"
  ]);
});

gulp.task('ace', function() {
  return gulp.src([
    'bower_components/angular/angular.js',
    'bower_components/angular-ui-ace/ui-ace.js',

    'bower_components/ace-builds/src-noconflict/ace.js',
    'bower_components/ace-builds/src-noconflict/mode-javascript.js',
    'bower_components/ace-builds/src-noconflict/worker-javascript.js',
    'bower_components/ace-builds/src-noconflict/theme-chrome.js',
    'bower_components/ace-builds/src-noconflict/snippets/text.js',
    'bower_components/ace-builds/src-noconflict/snippets/javascript.js'])
    .pipe(concat('scripts.js'))
    .pipe(uglify())
    .pipe(gulp.dest('js'));
});

INDEX.HTML

This is an HTML file that can reproduce the problem.

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <script type="text/javascript" src="js/scripts.js"></script>
    <script type="text/javascript">
        (function () {
            var ace = window.ace = window.ace || { };
            ace.initialize = function(editor) {
                ace.require("ace/ext/language_tools");

                editor.setTheme("ace/theme/chrome");
                editor.getSession().setMode("ace/mode/javascript");

                // options
                editor.setOptions({
                    showGutter: true,
                    showPrintMargin: false,
                    enableBasicAutocompletion: true,
                    enableSnippets: true,
                    enableLiveAutocompletion: true,
                    fontSize: '14px',
                    fontFamily: 'Consolas'
                });
            };
        })();
    </script>
    <script type="text/javascript">
        angular.module('app', ['ui.ace'])
                .controller('HomeController',['$scope', function($scope){
                    $scope.aceLoaded = function(_editor){
                        ace.initialize(_editor);
                    };

                    $scope.Model = {
                        Scripting: ""
                    }
                }]);
    </script>
</head>
<body ng-app="app" ng-controller="HomeController">
    <div ui-ace="{ onLoad: aceLoaded }"></div>
</body>
</html>

Solution

  • For ace.require to work you need to add ext-language_tools to your gulp.task("ace")

    'bower_components/ace-builds/src-noconflict/ace.js',
    'bower_components/ace-builds/src-noconflict/ext-language_tools.js',
    'bower_components/ace-builds/src-noconflict/mode-javascript.js',
    

    bower_components/ace-builds/src-noconflict/worker-javascript.js isn't needed in the bundle since it needs to be loaded in the webworker. For that to work you need to configure workerPath to point to the right folder

    ace.initialize = function(editor) {
        ace.require("ace/config").set("workerPath",
            "bower_components/ace-builds/src-min-noconflict");
        ace.require("ace/ext/language_tools");
    

    and for ace not being visible initially because of it's height being set to 0 see this question: Ace editor doesn't work with bootstrap

    also note that fontFamily: 'Consolas' might cause problems on systems where that font is missing, so it's better to add a fallback to monospace like fontFamily: 'Consolas', monospace