Code Readability
-
@jack-waugh I doubt anyone is going to want to take on that task given that the data in it is getting older.
Is it possible for you to just do a single pass of cleaning up things that are hard to read? A bit of renaming variables can go a long way, as can some comments and documentation. But I haven't see the code so I don't know.... is it available somewhere?
-
@rob I believe that variable names are not part of the problem. The main obstacle to readability, as I see it, is the oddball way I handled imports. The archive-presenting software was my first effort at an app built in JS from top to bottom (my earlier experience had been just with touching up the front end of a site whose back end was in Ruby, and did not involve imports; pretty much all the JS was in scripts that came up with the main HTML). When I coded the archive presenter, I had not come to understand yet, as I understand now, that some fairly straightforward conventions with
import
pathnames suffice to make it possible to use the same modules in the front end and the back end and they can get their dependencies the same way in either environment (pathnames begin "./"). So, without that understanding, and being a newb to importing modules, I put an oddball layer in to mediate the imports because I thought that was what it would take to make them work the same way in the front end and the back end.If I were the industrious sort and had all the time in the world, I would at least rewrite the code to use simple, conventional, idiomatic imports.
Maybe just a writeup explaining how the oddball import mechanism works would help anyone who might come along and want to modify the code.
The source code is published at https://bitbucket.org/voting-theory-forum/archive/
-
@jack-waugh Yeah seems simpler to address the import issue than rewrite from scratch. I think as long as it works, it should be ok staying as is.
-
@rob I have no experience in search-engine optimization (SEO). I'm getting this feedback from Google to the effect that the site is crap so far as Google is concerned. I gather that a sitemap is necessary. I don't know whether the sitemap has to list all the pages or just part of the top of the tree or what. I suppose I or someone could build one up that actually lists all the pages. I don't necessarily oppose your idea of having the domain name go directly to the current forum. I guess that would mean trashing the home page? I do think the archive should be published somewhere, and ideally, it should exhibit the nice SEO-oriented behavior as needed so it gets indexed and indeed ideally so that when someone searches on some concept that is mentioned in the archive, that the results come fairly high on Google's lists that it returns to people asking for searches, at least as high as is warranted given the age of the writings and depending maybe on whether there are more-recent mentions elsewhere of the subjects sought.
I guess if I am going to write up some explanation of the import mechanism, a .md (Markdown) file in the repo will be as good a place to publish it as any.
-
@jack-waugh You don't necessarily need a site map, but it needs to be able to find all the content by following links. I think if the top most page was the forum, it would be fine. Google can easily crawl nodebb forums.
I agree the archive is important, but it doesn't have to be on the front page, just a link on forum pages would be fine.
-
Here's an example of how I export the content of a module file:
export const asInstalled = async function ({mgr}) { try { await mgr.find("lib/pgm.mjs"); } catch (err) { console.log(err); return Promise.reject(err); }; return lib.lvar = { new: () => s.new_pvt().tell, // fundamental all: s.all, // combinator allBind: s.allBind, // combinator _: s, // debugging/testing bindToPromiseProc: s.bindToPromiseProc, // more combinators. fromPromiseProc: s.fromPromiseProc } }; ``` [OK, Markdown interpreter, I have the three backquotes on a line by themselves, and before and after are blank lines, so I don't know why you are not closing the code fragment and going to normal text here.] What I actually export is a function named `asInstalled`. My import mechanism calls this function to allow the module to "install" itself in the application and return what it really wants to export. There is a version of the import mechanism for the front end and a version for the server. But both call `asInstalled`. The parameter `{mgr}` mentioned in the code above is actually the import mechanism; I called it a "module manager", so "mgr" stands for "manager" in that notion. Passing it in allows the module to await the import of its dependencies. They all do this explicitly if they depend on other modules. They `await` the loading of those depenencies. Here is a snippet from the front-end version of the module manager, showing where it looks for and calls the `asInstalled` function exported by the target module. ``` const load$ = function (bare_path) { // Private procedure. console.log(`load ${bare_path}`); let full_path = "/" + bare_path; this.modules_by_bare_path[bare_path] = this.our_import(full_path).then( exports => { return exports.asInstalled ? exports.asInstalled({ mgr: this, }) : exports }); return undefined }; ``` Jack
-
When Google gives me SEO errors, I don't know that there's any easy way to share them so you can understand what it's saying.
-
@jack-waugh I've always used this super simple approach to import/export stuff in node.js. It's worked since node was new. No need to over-complicate stuff unless you are just showing off.
file: Blah.js
module.exports = { sayHello: () => { console.log('hello world'); }, sayGoodbye: () => { console.log('goodbye cruel world'); }, count: 0, showCount: () => { this.count++; console.log(this.count); } };
then in another file:
let blah = require('./Blah.js'); blah.sayHello();
-
@rob Node supports
require
, and I know how to use it there if necessary, but I believe that browsers don't natively. I think there is a package to do it, but given what year it is, I feel that use of ECMAimport
is more straightforward, as I do in the project that I am supposedly currently working on and in the framework under which I run it.For example, here are the imports from the top of the main module of the simulator.
import stdPrelude from "./utl/std_prelude.mjs"; import debug from "./utl/debug.mjs"; import CancellableWork from "./utl/cancellable_work.mjs"; import threads from "./utl/threads.mjs"; import overdom from "./utl/overdom.mjs"; import model_code from "./editable_model.mjs"; import problem_editor_view from "./problem_editor.mjs"; import validation from "./validation.mjs"; import solve from "./solve.mjs";
-
The bottom of the main module has this to export the entry points needed.
const exports = u.copy(); for (const name of t.exportNames) exports[name] = m[name]; export default exports;
Near the top, there's
const {m, u, t} = stdPrelude.clone(); t.exportNames = ['applicationStart', 'land'];
I use
m
for the work of the module,u
for utilities, andt
for temporary stuff.u.copy()
makes a copy of nothing; it is equivalent toObject.create(null)
. -
@jack-waugh And using import export is fine as well. It just seems that, in the code you posted at top (with async / await / promise stuff, exception handling, bind, tell, "combinators" etc) you are doing something else that is overly-complicated and unnecessary. At least it is unnecessary if all you are doing is trying to share code between files.
I don't put too much concern into what year it is. If something works, and it is simple to use and simple for others to understand, what value is added by doing something different?
I could teach a twelve year old my way of doing things in about 10 minutes. I consider that a plus.
-
@rob You
require(
...)
in the browser? -
@rob I agree that the import mechanism I used in the archive presenter is more complex than necessary. What I tend to do today is much simpler in most cases. As I said, I was approaching the tech as a newb back then, and hadn't come to understand the simpler way to go.
-
@jack-waugh said in Code Readability:
You require(...) in the browser?
Depends on what I'm working on. On big projects such as for work, sure. On my own smallish projects, rarely. I'm more likely to just do it like this:
File: Blah.js
Blah = { sayHello: () => { console.log('hello world'); }, sayGoodbye: () => { console.log('goodbye cruel world'); }, count: 0, showCount: () => { this.count++; console.log(this.count); } };
Then in another file:
Blah.sayHello();
-
@rob Sounds as though you are using some bundler that packages up your .js files and includes them in the HTML served.
-
@jack-waugh said in Code Readability:
Sounds as though you are using some bundler that packages up your .js files and includes them in the HTML served.
No. I've done things like that, when needed, but for simple projects there is no need.
All that is needed is:
<script src='Blah.js'></script> <script> Blah.sayHello(); </script>
where (again) Blah.js has the code like this:
Blah = { sayHello: () => { console.log('hello world'); } };
Blah is created as a global variable. I typically have one global per file. You could write it as window.Blah (which makes it more obvious it is a global in browser-space), or precede it with "var", but I often skip both of those for a clean appearance. I use upper case to make it clear that it is a global.
If I start getting worried about variable name collisions and such, then the project must have gotten huge.
I've done it sometimes like this when I worry about clashing with other variables when working in someone else's big messy app:
// create global "g" if it doesn't already exist window.g = window.g || {}; g.Blah = { sayHello: () => { console.log('hello world'); } };
And for completeness, I should add that sometimes I want a js file to run both client and server, so I might do something like this (which is admittedly a bit messier looking, but not a big deal):
{ let blah = { sayHello: () => { console.log('hello world'); } }; if(typeof window === 'undefined') { module.exports = blah; // node.js, export it } else { window.Blah = blah; // browser, make a global } }
-
@rob Yeah, I see how that would work. I had moved away from app-specific HTML for so long that I sort of forgot that you can load everything as scripts from the HTML. That technique obscures what is dependent on what. I can see that that might not matter much, but I am unused to thinking that way. I have each module import the stuff it needs.
For a future project, I may try moving from Node to Deno, which does not support
require
.My globals are
app
for the application andt
for temporary or testing. I used to only createt
by hand and not from code in a project or framework. But now I have a debugging aid callable from regular code and if called, it will uset
to place information where it can be easily gotten at via the REPL. I suppose if I wanted to be something-retentive, I could movet
toapp.t
orapp.debugging
or something. But I think future evolution of standards and tools is unlikely to grab any single-letter variables or assign meaning to them.My utility code that runs in both environments does not need to test for what environment it is running in; export works the same in either event.
The standard now has
globalThis
for the top-level global in any environment. -
@jack-waugh said in Code Readability:
That technique obscures what is dependent on what
Yeah, for stuff like that, I wait until a project becomes bigger to worry about it. It's easy to change it later if you need to be more rigorous about such things. For whatever it is worth, my suggestion is to relax a bit on such things, it might result in getting things working more quickly.
@jack-waugh said in Code Readability:
I had moved away from app-specific HTML for so long that I sort of forgot that you can load everything as scripts from the HTML.
I also do a fair amount of loading stuff this way. That is, load the script files dynamically. Can be really handy if you want to load an updated version of some code without restarting the whole app. This is a bit more complicated because it allows loading directly from a file (via src property of a script tag), or actually reading the text of the js file, and inserting that into a script tag.
(I wouldn't leave it this way on any kind of production app, but it is handy while building something out)
let loadAllJavascript = (isReload) => { var filteredPath = 'http://localhost:9999/js/filtered/'; // third item true to pull from sandbox files instead var files = [ ['ElemMaker', 'http://localhost:9999/js/library/', true], ['PopupBox', filteredPath, true], ['Scrubber', filteredPath, true], ['AccurateYoutubeTime', filteredPath, true], ['SynchronizerThread', filteredPath, true], ['VideoEventScheduler', filteredPath, true], ['VideoEventQueue', filteredPath, true], ['VideoPlayerNew', filteredPath, true] ]; for(var i=0; i<files.length; i++) { let scr = document.createElement('script'); if(files[i][2]) { if(!isReload) { // this (fetching the text then applying it // to script tag, rather than just setting the source // url) should not be necessary, but it seems to be // in this sandbox environment . fetch('./js/' + files[i][0] + '.js?' + Math.random()) .then((response) => { if (response.ok) return response.text() throw new Error('Network response was not ok.') }) .then((data) => { let scr = document.createElement('script'); scr.appendChild(document.createTextNode(data)) document.body.appendChild(scr); }); } } else { scr.src = files[i][1] + files[i][0] + '.js?' + Math.random(); document.body.appendChild(scr); } } }