Ho un Chrome Extension molto complesso che è diventato troppo grande per essere mantenuto nel suo formato attuale. Mi piacerebbe ristrutturarlo, ma ho 15 anni e questa è la prima webapp o estensione del suo tipo che ho creato, quindi non ho idea di come farlo.
TL; DR: ho una webapp grande / complessa che vorrei ristrutturare e non so come farlo. Devo seguire il mio attuale piano di ristrutturazione (sotto)? Suona come un buon punto di partenza, o c'è un approccio diverso che mi manca? Dovrei non fare nessuna delle cose che ho elencato?
Sebbene non sia pertinente alla domanda, il codice effettivo è Github e l'estensione è sul webstore .
La struttura di base è la seguente:
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();
}
Obiettivi della ristrutturazione
- Utilizzo della memoria: Chrome sembra avere una perdita di memoria nelle estensioni, stanno cercando di risolverlo ma la memoria continua ad aumentare ogni volta che viene caricata la pagina. Anche l'app usa molto da sola.
- Leggibilità del codice: A questo punto non riesco a seguire ciò che viene chiamato nel codice. Mentre riscrivo il codice, ho intenzione di commentare correttamente tutto.
- Interdipendenza del modulo: In questo momento i moduli si richiamano molto, AFAIK non va affatto bene dato che qualsiasi modifica apportata a un modulo potrebbe interessare innumerevoli altri.
- Tolleranza agli errori: al momento la tolleranza agli errori o la gestione degli errori sono molto ridotti. Se un widget sta causando il blocco del resto della pagina, l'utente dovrebbe almeno rimuoverlo.
La velocità non è attualmente un problema e mi piacerebbe mantenerla in questo modo.
Come penso che dovrei farlo
- La ristrutturazione dovrebbe essere eseguita utilizzando Backbone.js e gli eventi che chiamano i moduli (cioè su storage.loaded = > init).
- I moduli dovrebbero andare ognuno nel proprio file, penso che ci dovrebbe essere un insieme di file core su cui tutti i moduli possono fare affidamento e chiamare direttamente e tutto il resto dovrebbe essere basato su eventi.
- La struttura del widget dovrebbe essere mantenuta in gran parte uguale, ma forse dovrebbero anche essere suddivisi nei propri file.
- AFAIK non puoi caricare tutti i modelli in una cartella, quindi devono rimanere in linea.
- Grunt dovrebbe essere usato per unire tutti i moduli, plugin e widget in un unico file. Anche i modelli dovrebbero essere precompilati.
Domanda:
Devo seguire il mio attuale piano di ristrutturazione? Suona come un buon punto di partenza, o c'è un approccio diverso che mi manca? Dovrei non fare nessuna delle cose che ho elencato?
Le applicazioni scritte con Backbone tendono ad essere più intensive (memoria e velocità) di quelle scritte in Vanilla JS?
Inoltre, posso aspettarmi di migliorare questo con una ristrutturazione adeguata o il mio codice attuale è buono come ci si può aspettare?