html_url,issue_url,id,node_id,user,created_at,updated_at,author_association,body,reactions,issue,performed_via_github_app https://github.com/simonw/datasette/issues/987#issuecomment-752714747,https://api.github.com/repos/simonw/datasette/issues/987,752714747,MDEyOklzc3VlQ29tbWVudDc1MjcxNDc0Nw==,9599,2020-12-30T18:23:08Z,2020-12-30T18:23:20Z,OWNER,"In terms of ""places to put your plugin content"", the simplest solution I can think of is something like this: ```html
``` Alternative designs: - A documented JavaScript function that returns the CSS selector where plugins should put their content - A documented JavaScript function that returns a DOM node where plugins should put their content. This would allow the JavaScript to create the element if it does not already exist (though it wouldn't be obvious WHERE that element should be created) - Documented JavaScript functions for things like ""append this node/HTML to the place-where-plugins-go"" I think the original option - an empty `
` with a known `id` attribute - is the right one to go with here. It's the simplest, it's very easy for custom template authors to understand and it acknowledges that plugins may have all kinds of extra crazy stuff they want to do - like checking in that div to see if another plugin has written to it already, for example.","{""total_count"": 1, ""+1"": 1, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712984738, https://github.com/simonw/datasette/issues/987#issuecomment-752697279,https://api.github.com/repos/simonw/datasette/issues/987,752697279,MDEyOklzc3VlQ29tbWVudDc1MjY5NzI3OQ==,9599,2020-12-30T17:23:27Z,2020-12-30T17:23:32Z,OWNER,Related problem: right now if you're writing custom template it's not at all obvious how to write them such that visualization plugins like `datasette-cluster-map` will continue to work.,"{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712984738, https://github.com/simonw/datasette/issues/987#issuecomment-752696499,https://api.github.com/repos/simonw/datasette/issues/987,752696499,MDEyOklzc3VlQ29tbWVudDc1MjY5NjQ5OQ==,9599,2020-12-30T17:21:08Z,2020-12-30T17:21:08Z,OWNER,"More generally, I need to document certain areas of the page that JavaScript plugins are invited to append their content to - such that plugin authors can use them and feel confident that future changes to the Datasette templates won't break their plugins.","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712984738, https://github.com/simonw/datasette/issues/983#issuecomment-752773508,https://api.github.com/repos/simonw/datasette/issues/983,752773508,MDEyOklzc3VlQ29tbWVudDc1Mjc3MzUwOA==,9599,2020-12-30T22:10:08Z,2020-12-30T22:11:34Z,OWNER,"https://twitter.com/dracos/status/1344402639476424706 points out that plugins returning 0 will be ignored. This should probably check for `result !== undefined` instead - knocks the size up to 250.","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429, https://github.com/simonw/datasette/issues/983#issuecomment-752770488,https://api.github.com/repos/simonw/datasette/issues/983,752770488,MDEyOklzc3VlQ29tbWVudDc1Mjc3MDQ4OA==,9599,2020-12-30T21:55:35Z,2020-12-30T21:58:26Z,OWNER,"This one minifies to 241: ```javascript var datasette = datasette || {}; datasette.plugins = (() => { var registry = {}; return { register: (hook, fn, parameters) => { if (!registry[hook]) { registry[hook] = []; } registry[hook].push([fn, parameters]); }, call: (hook, args) => { args = args || {}; var results = []; (registry[hook] || []).forEach(([fn, parameters]) => { /* Call with the correct arguments */ var result = fn.apply(fn, parameters.map(parameter => args[parameter])); if (result) { results.push(result); } }); return results; } }; })(); ``` `var datasette=datasette||{};datasette.plugins=(()=>{var a={};return{register:(t,r,e)=>{a[t]||(a[t]=[]),a[t].push([r,e])},call:(t,r)=>{r=r||{};var e=[];return(a[t]||[]).forEach(([a,t])=>{var s=a.apply(a,t.map(a=>r[a]));s&&e.push(s)}),e}}})();`","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429, https://github.com/simonw/datasette/issues/983#issuecomment-752770133,https://api.github.com/repos/simonw/datasette/issues/983,752770133,MDEyOklzc3VlQ29tbWVudDc1Mjc3MDEzMw==,9599,2020-12-30T21:53:45Z,2020-12-30T21:54:22Z,OWNER,"FixMyStreet inlines some JavaScript, and it's always a good idea to copy what they're doing when it comes to web performance: https://github.com/mysociety/fixmystreet/blob/23e9564b58a86b783ce47f3c0bf837cbd4fe7282/templates/web/base/common_header_tags.html#L19-L25 Note `var fixmystreet=fixmystreet||{};` which is shorter - https://twitter.com/dracos/status/1344399909794045954","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429, https://github.com/simonw/datasette/issues/983#issuecomment-752767500,https://api.github.com/repos/simonw/datasette/issues/983,752767500,MDEyOklzc3VlQ29tbWVudDc1Mjc2NzUwMA==,9599,2020-12-30T21:42:07Z,2020-12-30T21:42:07Z,OWNER,"Another option: have both ""dev"" and ""production"" versions of the plugin mechanism script. Make it easy to switch between the two. Build JavaScript unit tests that exercise the ""production"" APIs against the development version, and have extra tests that just work against the features in the development version.","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429, https://github.com/simonw/datasette/issues/983#issuecomment-752767174,https://api.github.com/repos/simonw/datasette/issues/983,752767174,MDEyOklzc3VlQ29tbWVudDc1Mjc2NzE3NA==,9599,2020-12-30T21:40:44Z,2020-12-30T21:40:44Z,OWNER,Started a Twitter thread about this here: https://twitter.com/simonw/status/1344392603794477056,"{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429, https://github.com/simonw/datasette/issues/983#issuecomment-752760815,https://api.github.com/repos/simonw/datasette/issues/983,752760815,MDEyOklzc3VlQ29tbWVudDc1Mjc2MDgxNQ==,9599,2020-12-30T21:15:41Z,2020-12-30T21:15:41Z,OWNER,"I'm going to write a few example plugins and try them out against the longer and shorter versions of the script, to get a better feel for how useful the longer versions with the error handling and explicit definition actually are.","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429, https://github.com/simonw/datasette/issues/983#issuecomment-752760054,https://api.github.com/repos/simonw/datasette/issues/983,752760054,MDEyOklzc3VlQ29tbWVudDc1Mjc2MDA1NA==,9599,2020-12-30T21:12:36Z,2020-12-30T21:14:05Z,OWNER,"I gotta admit that 262 byte version is pretty tempting, if it's going to end up in the `` of every single page.","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429, https://github.com/simonw/datasette/issues/983#issuecomment-752759885,https://api.github.com/repos/simonw/datasette/issues/983,752759885,MDEyOklzc3VlQ29tbWVudDc1Mjc1OTg4NQ==,9599,2020-12-30T21:11:52Z,2020-12-30T21:14:00Z,OWNER,"262 bytes if I remove the parameter introspection code, instead requiring plugin authors to specify the arguments they take: ```javascript window.datasette = window.datasette || {}; window.datasette.plugins = (() => { var registry = {}; return { register: (hook, fn, parameters) => { if (!registry[hook]) { registry[hook] = []; } registry[hook].push([fn, parameters]); }, call: (hook, args) => { args = args || {}; var results = []; (registry[hook] || []).forEach(([fn, parameters]) => { /* Call with the correct arguments */ var callWith = parameters.map(parameter => args[parameter]); var result = fn.apply(fn, callWith); if (result) { results.push(result); } }); return results; } }; })(); ``` `window.datasette=window.datasette||{},window.datasette.plugins=(()=>{var a={};return{register:(t,e,r)=>{a[t]||(a[t]=[]),a[t].push([e,r])},call:(t,e)=>{e=e||{};var r=[];return(a[t]||[]).forEach(([a,t])=>{var s=t.map(a=>e[a]),d=a.apply(a,s);d&&r.push(d)}),r}}})();`","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429, https://github.com/simonw/datasette/issues/983#issuecomment-752758802,https://api.github.com/repos/simonw/datasette/issues/983,752758802,MDEyOklzc3VlQ29tbWVudDc1Mjc1ODgwMg==,9599,2020-12-30T21:07:33Z,2020-12-30T21:10:10Z,OWNER,"Removing the `datasette.plugin.define()` method and associated error handling reduces the uglified version from 683 bytes to 380 bytes. I think the error checking is worth the extra 303 bytes per page load, even if it's only really needed for a better developer experience. ```javascript window.datasette = window.datasette || {}; window.datasette.plugins = (() => { var registry = {}; function extractParameters(fn) { var match = /\((.*)\)/.exec(fn.toString()); if (match && match[1].trim()) { return match[1].split(',').map(s => s.trim()); } else { return []; } } return { register: (hook, fn, parameters) => { parameters = parameters || extractParameters(fn); if (!registry[hook]) { registry[hook] = []; } registry[hook].push([fn, parameters]); }, call: (hook, args) => { args = args || {}; var implementations = registry[hook] || []; var results = []; implementations.forEach(([fn, parameters]) => { /* Call with the correct arguments */ var callWith = parameters.map(parameter => args[parameter]); var result = fn.apply(fn, callWith); if (result) { results.push(result); } }); return results; } }; })(); ``` `window.datasette=window.datasette||{},window.datasette.plugins=(()=>{var t={};return{register:(r,a,e)=>{e=e||function(t){var r=/\((.*)\)/.exec(t.toString());return r&&r[1].trim()?r[1].split("","").map(t=>t.trim()):[]}(a),t[r]||(t[r]=[]),t[r].push([a,e])},call:(r,a)=>{a=a||{};var e=t[r]||[],i=[];return e.forEach(([t,r])=>{var e=r.map(t=>a[t]),n=t.apply(t,e);n&&i.push(n)}),i}}})();`","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429, https://github.com/simonw/datasette/issues/983#issuecomment-752757289,https://api.github.com/repos/simonw/datasette/issues/983,752757289,MDEyOklzc3VlQ29tbWVudDc1Mjc1NzI4OQ==,9599,2020-12-30T21:02:20Z,2020-12-30T21:02:20Z,OWNER,I'm going to need to add JavaScript unit tests for this new plugin system.,"{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429, https://github.com/simonw/datasette/issues/983#issuecomment-752751490,https://api.github.com/repos/simonw/datasette/issues/983,752751490,MDEyOklzc3VlQ29tbWVudDc1Mjc1MTQ5MA==,9599,2020-12-30T20:40:04Z,2020-12-30T21:34:22Z,OWNER,"This one is 683 bytes with Uglify - I like how https://skalman.github.io/UglifyJS-online/ shows you the minified character count as you edit the script: ```javascript window.datasette = window.datasette || {}; window.datasette.plugins = (() => { var registry = {}; var definitions = {}; var stringify = JSON.stringify; function extractParameters(fn) { var match = /\((.*)\)/.exec(fn.toString()); if (match && match[1].trim()) { return match[1].split(',').map(s => s.trim()); } else { return []; } } function isSubSet(a, b) { return a.every(parameter => b.includes(parameter)) } return { _r: registry, define: (hook, parameters) => { definitions[hook] = parameters || []; }, register: (hook, fn, parameters) => { parameters = parameters || extractParameters(fn); if (!definitions[hook]) { throw 'Hook ""' + hook + '"" not defined'; } /* Check parameters is a subset of definitions[hook] */ var validParameters = definitions[hook]; if (!isSubSet(parameters, validParameters)) { throw '""' + hook + '"" valid args: ' + stringify(validParameters); } if (!registry[hook]) { registry[hook] = []; } registry[hook].push([fn, parameters]); }, call: (hook, args) => { args = args || {}; if (!definitions[hook]) { throw '""' + hook + '"" hook not defined'; } if (!isSubSet(Object.keys(args), definitions[hook])) { throw '""' + hook + '"" valid args: ' + stringify(definitions[hook]); } var implementations = registry[hook] || []; var results = []; implementations.forEach(([fn, parameters]) => { /* Call with the correct arguments */ var callWith = parameters.map(parameter => args[parameter]); var result = fn.apply(fn, callWith); if (result) { results.push(result); } }); return results; } }; })(); ``` `window.datasette=window.datasette||{},window.datasette.plugins=(()=>{var t={},r={},e=JSON.stringify;function i(t,r){return t.every(t=>r.includes(t))}return{_r:t,define:(t,e)=>{r[t]=e||[]},register:(a,n,o)=>{if(o=o||function(t){var r=/\((.*)\)/.exec(t.toString());return r&&r[1].trim()?r[1].split("","").map(t=>t.trim()):[]}(n),!r[a])throw'Hook ""'+a+'"" not defined';var d=r[a];if(!i(o,d))throw'""'+a+'"" valid args: '+e(d);t[a]||(t[a]=[]),t[a].push([n,o])},call:(a,n)=>{if(n=n||{},!r[a])throw'""'+a+'"" hook not defined';if(!i(Object.keys(n),r[a]))throw'""'+a+'"" valid args: '+e(r[a]);var o=t[a]||[],d=[];return o.forEach(([t,r])=>{var e=r.map(t=>n[t]),i=t.apply(t,e);i&&d.push(i)}),d}}})();`","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429, https://github.com/simonw/datasette/issues/983#issuecomment-752750551,https://api.github.com/repos/simonw/datasette/issues/983,752750551,MDEyOklzc3VlQ29tbWVudDc1Mjc1MDU1MQ==,9599,2020-12-30T20:36:38Z,2020-12-30T20:37:48Z,OWNER,"This version minifies to 702 characters: ```javascript window.datasette = window.datasette || {}; window.datasette.plugins = (() => { var registry = {}; var definitions = {}; var stringify = JSON.stringify; function extractParameters(fn) { var match = /\((.*)\)/.exec(fn.toString()); if (match && match[1].trim()) { return match[1].split(',').map(s => s.trim()); } else { return []; } } function isSubSet(a, b) { return a.every(parameter => b.includes(parameter)) } return { _registry: registry, define: (hook, parameters) => { definitions[hook] = parameters || []; }, register: (hook, fn, parameters) => { parameters = parameters || extractParameters(fn); if (!definitions[hook]) { throw '""' + hook + '"" is not a defined hook'; } /* Check parameters is a subset of definitions[hook] */ var validParameters = definitions[hook]; if (!isSubSet(parameters, validParameters)) { throw '""' + hook + '"" valid args are ' + stringify(validParameters); } if (!registry[hook]) { registry[hook] = []; } registry[hook].push([fn, parameters]); }, call: (hook, args) => { args = args || {}; if (!definitions[hook]) { throw '""' + hook + '"" hook is not defined'; } if (!isSubSet(Object.keys(args), definitions[hook])) { throw '""' + hook + '"" valid args: ' + stringify(definitions[hook]); } var implementations = registry[hook] || []; var results = []; implementations.forEach(([fn, parameters]) => { /* Call with the correct arguments */ var callWith = parameters.map(parameter => args[parameter]); var result = fn.apply(fn, callWith); if (result) { results.push(result); } }); return results; } }; })(); ``` Or 701 characters using https://skalman.github.io/UglifyJS-online/","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429, https://github.com/simonw/datasette/issues/983#issuecomment-752749189,https://api.github.com/repos/simonw/datasette/issues/983,752749189,MDEyOklzc3VlQ29tbWVudDc1Mjc0OTE4OQ==,9599,2020-12-30T20:31:28Z,2020-12-30T20:31:28Z,OWNER,"Using raw string exceptions, `throw '""' + hook + '"" hook has not been defined';`, knocks it down to 795 characters.","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429, https://github.com/simonw/datasette/issues/983#issuecomment-752748496,https://api.github.com/repos/simonw/datasette/issues/983,752748496,MDEyOklzc3VlQ29tbWVudDc1Mjc0ODQ5Ng==,9599,2020-12-30T20:28:48Z,2020-12-30T20:28:48Z,OWNER,If I'm going to minify it I'll need to figure out a build step in Datasette itself so that I can easily work on that minified version.,"{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",712260429, https://github.com/simonw/datasette/issues/983#issuecomment-752747999,https://api.github.com/repos/simonw/datasette/issues/983,752747999,MDEyOklzc3VlQ29tbWVudDc1Mjc0Nzk5OQ==,9599,2020-12-30T20:27:00Z,2020-12-30T20:27:00Z,OWNER,"I need to decide how this code is going to be loaded. Putting it in a blocking `