7

I have a very complex Chrome Extension that has gotten too large to maintain in its current format. I'd like to restructure it, but I'm 15 and this is the first webapp or extension of it's type I've built so I have no idea how to do it.

TL;DR: I have a large/complex webapp I'd like to restructure and I don't know how to do it. Should I follow my current restructure plan (below)? Does that sound like a good starting point, or is there a different approach that I'm missing? Should I not do any of the things I listed?

While it isn't relevant to the question, the actual code is on Github and the extension is on the webstore.

The basic structure is as follows:

index.html

<html>
    <head>
        <link href="css/style.css" rel="stylesheet" /> <!-- This holds the main app styles -->
        <link href="css/widgets.css" rel="stylesheet" /> <!-- And this one holds widget styles -->
    </head>

    <body class="unloaded">
        <!-- Low-level base elements are "hardcoded" here, the unloaded class is used for transitions and is removed on load. i.e: -->

        <div class="tab-container" tabindex="-1">           
            <!-- Tab nav -->
        </div>

        <!--
            Templates for all parts of the application and widgets are stored as elements here.
            I plan on changing these to <script> elements during the restructure since <template>'s need valid HTML.
        -->
        <template id="template.toolbar">
            <!-- Template content -->
        </template>

        <!-- Templates end -->

        <!-- Plugins -->
        <script type="text/javascript" src="js/plugins.js"></script>

        <!-- This contains the code for all widgets, I plan on moving this online and downloading as necessary soon. -->
        <script type="text/javascript" src="js/widgets.js"></script>

        <!-- This contains the main application JS. -->
        <script type="text/javascript" src="js/script.js"></script>
    </body>
</html>

widgets.js

(initLog || (window.initLog = [])).push([new Date().getTime(), "A log is kept during page load so performance can be analyzed and errors pinpointed"]);

// Widgets are stored in an object and extended (with jQuery, but I'll probably switch to underscore if using Backbone) as necessary
var Widgets = {
    1: { // Widget ID, this is set here so widgets can be retreived by ID
        id: 1, // Widget ID again, this is used after the widget object is duplicated and detached
        size: 3, // Default size, medium in this case
        order: 1, // Order shown in "store"
        name: "Weather", // Widget name
        interval: 300000, // Refresh interval
        nicename: "weather", // HTML and JS safe widget name
        sizes: ["tiny", "small", "medium"], // Available widget sizes
        desc: "Short widget description",
        settings: [
            { // Widget setting specifications stored as an array of objects.  These are used to dynamically generate widget setting popups.
                type: "list",
                nicename: "location",
                label: "Location(s)",
                placeholder: "Enter a location and press Enter"
            }
        ],
        config: { // Widget settings as stored in the tabs object (see script.js for storage information)
            size: "medium",
            location: ["San Francisco, CA"]
        },
        data: {}, // Cached widget data stored locally, this lets it work offline
        customFunc: function(cb) {}, // Widgets can optionally define custom functions in any part of their object
        refresh: function() {}, // This fetches data from the web and caches it locally in data, then calls render. It gets called after the page is loaded for faster loads
        render: function() {} // This renders the widget only using information from data, it's called on page load.
    }
};

script.js

(initLog || (window.initLog = [])).push([new Date().getTime(), "These are also at the end of every file"]);

// Plugins, extends and globals go here. i.e. Number.prototype.pad = ....

var iChrome = function(refresh) { // The main iChrome init, called with refresh when refreshing to not re-run libs
    iChrome.Status.log("Starting page generation"); // From now on iChrome.Status.log is defined, it's used in place of the initLog

    iChrome.CSS(); // Dynamically generate CSS based on settings

    iChrome.Tabs(); // This takes the tabs stored in the storage (see fetching below) and renders all columns and widgets as necessary

    iChrome.Status.log("Tabs rendered"); // These will be omitted further along in this excerpt, but they're used everywhere

    // Checks for justInstalled => show getting started are run here

    /* The main init runs the bare minimum required to display the page, this sets all non-visible or instantly need things (such as widget dragging) on a timeout */
    iChrome.deferredTimeout = setTimeout(function() {
        iChrome.deferred(refresh); // Pass refresh along, see above
    }, 200);
};

iChrome.deferred = function(refresh) {}; // This calls modules one after the next in the appropriate order to finish rendering the page

iChrome.Search = function() {}; // Modules have a base init function and are camel-cased and capitalized

iChrome.Search.submit = function(val) {}; // Methods within modules are camel-cased and not capitalized


/*
  Extension storage is async and fetched at the beginning of plugins.js, it's then stored in a variable that iChrome.Storage processes.

  The fetcher checks to see if processStorage is defined, if it is it gets called, otherwise settings are left in iChromeConfig
*/
var processStorage = function() {
    iChrome.Storage(function() {
        iChrome.Templates(); // Templates are read from their elements and held in a cache

        iChrome(); // Init is called
    });
};

if (typeof iChromeConfig == "object") {
    processStorage();
}

Objectives of the restructure

  1. Memory usage: Chrome apparently has a memory leak in extensions, they're trying to fix it but memory still keeps on getting increased every time the page is loaded. The app also uses a lot on its own.
  2. Code readability: At this point I can't follow what's being called in the code. While rewriting the code I plan on properly commenting everything.
  3. Module interdependence: Right now modules call each other a lot, AFAIK that's not good at all since any change you make to one module could affect countless others.
  4. Fault tolerance: There's very little fault tolerance or error handling right now. If a widget is causing the rest of the page to stop rendering the user should at least be able to remove it.

Speed is currently not an issue and I'd like to keep it that way.

How I think I should do it

  • The restructure should be done using Backbone.js and events that call modules (i.e. on storage.loaded => init).
  • Modules should each go in their own file, I'm thinking there should be a set of core files that all modules can rely on and call directly and everything else should be event based.
  • Widget structure should be kept largely the same, but maybe they should also be split into their own files.
  • AFAIK you can't load all templates in a folder, therefore they need to stay inline.
  • Grunt should be used to merge all modules, plugins and widgets into one file. Templates should also all be precompiled.

Question:

Should I follow my current restructure plan? Does that sound like a good starting point, or is there a different approach that I'm missing? Should I not do any of the things I listed?

Do applications written with Backbone tend to be more intensive (memory and speed) than ones written in Vanilla JS?

Also, can I expect to improve this with a proper restructure or is my current code about as good as can be expected?

  • 1
    I take that edit to mean this is the right place. Finally! – A.M.K May 27 '14 at 17:37
  • 1
    I don't know if it is or not. I'm debating voting to close because I really don't see an explicit problem for us to help you solve. Sounds like you're soliciting opinions. Perhaps you should check out CodeReview.StackExchange with snippets of your code? We do definitely talk about design stuff here and would love to help you come up with solutions to design problems, but I don't see an explicit design problem here, just sounds like you have a big messy project (as many of us do) and want some advice on how to correct it. SE isn't so good at advice, better at direct answers to direct questions – Jimmy Hoffa May 27 '14 at 17:40
  • 1
    I actually posted this on Code Review and was sent here. I guess the real question is "Is what I think I should do right?". – A.M.K May 27 '14 at 17:45
  • 1
    @RobertHarvey he probably asked a more design based question on CR.SE, or gave them significantly more code than they really accept. – Jimmy Hoffa May 27 '14 at 17:51
  • 1
    @GlenH7: Do you have an hour or two to devote to the answer? – Robert Harvey May 27 '14 at 17:58
2

Memory usage: Chrome apparently has a memory leak in extensions, they're trying to fix it but memory still keeps on getting increased every time the page is loaded. The app also uses a lot on its own.

You need to define what "alot" is, and what "too much" is. Memory usage is only a problem when there isn't enough. Investigating memory problems has little to do with refactoring. You need to locate any offending code and plug the leak. If you know your code is leaking memory, than I would try to isolate the offending areas and go to stackoverflow.com to seek advise on fixing it.

Code readability: At this point I can't follow what's being called in the code. While rewriting the code I plan on properly commenting everything.

This is a very subjective issue. Code structure, source code comments and code readability are all matters of opinion.

Module interdependence: Right now modules call each other a lot, AFAIK that's not good at all since any change you make to one module could affect countless others.

Now this is where we can help you. There are multiple platforms and tools out there for Javascript developers to address these issues. There are also multiple design patterns that address these issues. There are package builders, code compilers and dependency injectors.

I use AngularJS with Google's Closure compiler. Angular gives me dependency injection, and closure gives me compile time warnings and packaging.

You'll have to decide what tools work for you. Trust me. It's a painful process to pick a tool.

Fault tolerance: There's very little fault tolerance or error handling right now. If a widget is causing the rest of the page to stop rendering the user should at least be able to remove it.

This is a high-level requirement. It's something to "keep in mind" when you are rewriting it.

Do applications written with Backbone tend to be more intensive (memory and speed) than ones written in Vanilla JS?

No. Backbone does not offer anything that addresses your specific issues. It's designed to be a light-weight library that offers you only a couple of base classes to mimic a MVC pattern. The developers wanted something that had a very light-weight foot-print. I don't think a Chrome extension needs to be light-weight. It's installed on the PC and runs local. You can afford to go with something heavier and offers a more robust framework.

Also, can I expect to improve this with a proper restructure or is my current code about as good as can be expected?

You've made some basic mistakes if fixed will greatly improve your time maintaining the code.

I could create a list of issues, but that's not the point of this answer.

What you want to learn is the building blocks of highly maintainable code.

  • Have you put code into easy to locate files, or is everything in one big file?
  • Can you tell by looking at your files what is "new untested code" and "old reliable code"?
  • Can you test it?
  • Does each file have a clear name and contain code that is clear of it's purpose.
  • Is each file simple. Short and to the point.
  • Are your functions written to prevent misuse.

The list goes on like that, but hopefully you get the idea.

  • So essentially, the framework doesn't matter so much, just keep the code separated, organized and well-written (error handling, etc.). – A.M.K May 27 '14 at 18:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.