${name}
Drag this button to your bookmarks bar to save it as a bookmarklet:
${ options.repo ? `See source at ${options.repo}
` : '' }This page was created with the bookmarklet npm library.
Repository: mrcoles/bookmarklet Branch: master Commit: 0600069a0520 Files: 14 Total size: 17.8 KB Directory structure: gitextract_epxp27wt/ ├── .gitignore ├── .prettierrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── bin/ │ └── cli.js ├── bookmarklet.js ├── package.json └── test/ ├── bookmarklets/ │ ├── test1.bookmarklet.js │ └── test2.bookmarklet.js ├── expected/ │ ├── test1.bookmarklet.js │ └── test2.bookmarklet.js └── run.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ node_modules test/actual/ ================================================ FILE: .prettierrc ================================================ singleQuote: true arrowParens: avoid trailingComma: none ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - "6" - "node" ================================================ FILE: CHANGELOG.md ================================================ # 3.0.0 - Changed minifier to Terser - Remove Babel transform to avoid issues such as regenerator-runtime not found (do any needed transforms before this script) ================================================ FILE: LICENSE.txt ================================================ The MIT License Copyright (c) 2021 Peter Coles (http://mrcoles.com/) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Bookmarklet: sane development, familiar format [](https://travis-ci.org/mrcoles/bookmarklet) [](https://github.com/prettier/prettier) Bookmarklet is a nodejs module for compiling bookmarklets in server-side code and directly from the shell. You can run it on any JavaScript file—it will minify it using uglify-js, wrap it in a self executing function, and return an escaped bookmarklet. More so, it supports a metadata block—modeled after the [greasemonkey userscript metadata block](http://wiki.greasespot.net/Metadata_Block)—to specify metadata, external stylesheets and script includes, which can look like this: // ==Bookmarklet== // @name LoveGames // @author Old Gregg // @style !loadOnce https://mrcoles.com/media/css/silly.css // @script https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js // ==/Bookmarklet== Most notably, you can specify any external scripts that you’d like your bookmarklet to include via the `@script` rule, which can be repeated as many times as you’d like. NOTE: currently with script includes you have to handle `noConflict` scenarios yourself, e.g., you might want to start off a script with `var $ = jQuery.noConflict(true)`. In addition, any css files included with `@style` will be injected. By default, every time the bookmark is hit, it will add the script and style tags again. You customize each one per line by adding a `!loadOnce` declaration between the `@style` or `@script` param and the path for the asset. See the example above. As of v1.0.0, this now uses Babel with the present "env" to make the code backwards compatible before minifying it. This project is open to suggestions & pull requests. Also, if you’re just looking for a quick way to throw together a bookmarklet, try my [browser-based bookmarklet creator](http://mrcoles.com/bookmarklet/). ### Installation The dependency can be found on [NPM as “bookmarklet”](https://www.npmjs.org/package/bookmarklet). You can install it with: ```bash npm install bookmarklet ``` ### Usage You can easily see usage by running `bookmarklet -h`: ```bash > bookmarklet -h Bookmarklet v0.0.1 usage: bookmarklet [-d | --demo] source [destination] -d | --demo - output a demo HTML page for sharing the bookmarklet source - path to file to read from or `-` for stdin destination - path to file to write to ``` The default output is the raw bookmarlet code. _NEW_ add the `--demo` flag to output a test HTML page that includes the bookmarklet on it. ### Testing A very basic test script can be run via `bash test/run.sh` ================================================ FILE: bin/cli.js ================================================ #!/usr/bin/env node const path = require('path'); const fs = require('fs'); const bookmarklet = require('../bookmarklet'); // // Input parsing // let args = process.argv.slice(2); if (['-V', '--version'].some(flag => args.indexOf(flag) !== -1)) { console.log(bookmarklet.version.join('.')); process.exit(0); } function help() { console.error(` Bookmarklet v${bookmarklet.version.join('.')} Usage: bookmarklet [options] source [destination] source path to file to read from or - for stdin destination path to file to write to Options: -d, --demo generate a demo HTML page More info: https://github.com/mrcoles/bookmarklet `); } function die(msg) { msg && console.error(`[ERROR] bookmarklet: ${msg}`); process.exit(1); } function warn(msg) { console.error(`[WARN] bookmarklet: ${msg}`); } // flags const _isArgDemo = arg => arg === '-d' || arg === '--demo'; const makeDemo = args.some(_isArgDemo); args = args.filter(arg => !_isArgDemo(arg)); // help if (args.length == 0 || args.some(arg => arg === '-h' || arg === '--help')) { help(); process.exit(0); } // file paths if (args.length > 2) { die('invalid arguments, run with --help to see usage.\n\n'); } let source = args[0]; let destination = args[1]; const readStdin = source === '-'; if (source && source[0] !== '/' && !readStdin) { source = path.join(process.cwd(), source); } if (destination) { if (destination[0] !== '/') { destination = path.join(process.cwd(), destination); } let isDirectory = destination.endsWith('/'); if (!isDirectory) { try { let destStat = fs.statSync(destination); isDirectory = destStat.isDirectory(); } catch (e) {} } if (isDirectory) { if (readStdin) { die('must name output file if reading from stdin\n\n'); } let filename = path.basename(source); destination = path.join(destination, filename); } } // // Main // function dataCallback(e, data) { if (e) { die(e.message); } data = bookmarklet.parseFile(data); if (data.errors) { die(data.errors.join('\n')); } return bookmarklet .convert(data.code, data.options) .then(code => { if (makeDemo) { code = bookmarklet.makeDemo(code, data.options); } if (destination) { fs.writeFileSync(destination, code); } else { console.log(code); } }) .catch(e => { die(e); }); } if (source !== '-') { fs.readFile(source, 'utf8', dataCallback); } else { process.stdin.resume(); process.stdin.setEncoding('utf8'); var buffer = ''; process.stdin.on('data', data => (buffer += data)); process.stdin.on('end', () => dataCallback(false, buffer)); } ================================================ FILE: bookmarklet.js ================================================ const version = [3, 0, 0]; // const babel = require('@babel/core'); // const babelPresetEnv = require('@babel/preset-env'); const md5 = require('md5'); const Terser = require('terser'); // metadata const str = 1; const list = 2; const bool = 3; const metadata = { types: { string: str, list: list, boolean: bool }, keys: { name: str, version: str, description: str, repository: str, author: str, email: str, url: str, license: str, script: list, style: list } }; function quoteEscape(x) { return x.replace('"', '\\"').replace("'", "\\'"); } function extractOptions(path) { // Returns { // path: the updated path string (minus any options) // opts: plain object of options // } // // You can prefix a path with options in the form of: // // ``` // @style !loadOnce !foo=false https://example.com/foo.css // ``` // // If there is no `=`, then the value of the option defaults to `true`. // Values get converted via JSON.parse if possible, o/w they're a string. // let opts = {}; while (true) { let m = path.match(/^(\![^\s]+)\s+/); if (m) { path = path.substring(m.index + m[0].length); let opt = m[1].substring(1).split('='); opts[opt[0]] = opt[1] === undefined ? true : _fuzzyParse(opt[1]); } else { break; } } return { path, opts }; } const _fuzzyParse = val => { try { return JSON.parse(val); } catch (e) { return val; } }; function loadScript(code, path, loadOnce) { loadOnce = !!loadOnce; let id = `bookmarklet__script_${md5(path).substring(0, 7)}`; return ` function callback(){ ${code} } if (!${loadOnce} || !document.getElementById("${id}")) { var s = document.createElement("script"); if (s.addEventListener) { s.addEventListener("load", callback, false) } else if (s.readyState) { s.onreadystatechange = callback } if (${loadOnce}) { s.id = "${id}"; } s.src = "${quoteEscape(path)}"; document.body.appendChild(s); } else { callback(); } `; } function loadStyle(code, path, loadOnce) { loadOnce = !!loadOnce; let id = `bookmarklet__style_${md5(path).substring(0, 7)}`; return `${code} if (!${loadOnce} || !document.getElementById("${id}")) { var link = document.createElement("link"); if (${loadOnce}) { link.id = "${id}"; } link.rel="stylesheet"; link.href = "${quoteEscape(path)}"; document.body.appendChild(link); } `; } async function minify(code) { // const result = babel.transform(code, { // presets: [ // [ // babelPresetEnv, // { // targets: 'ie 8', // '> 0.25%, not dead', // corejs: { version: '3.9', proposals: true }, // useBuiltIns: 'usage' // } // ] // ] // }); const result = await Terser.minify(code); return result.code; } async function convert(code, options) { code = await minify(code); let stylesCode = ''; if (options.script) { options.script = options.script.reverse(); options.script.forEach(s => { let { path, opts } = extractOptions(s); code = loadScript(code, path, opts.loadOnce); }); code = await minify(code); } if (options.style) { options.style.forEach(s => { let { path, opts } = extractOptions(s); stylesCode = loadStyle(stylesCode, path, opts.loadOnce); }); const minifiedStyles = await minify(stylesCode); code = minifiedStyles + code; } code = `(function(){${code}})()`; return `javascript:${encodeURIComponent(code)}`; } function parseFile(data) { let inMetadataBlock = false; let openMetadata = '==Bookmarklet=='; let closeMetadata = '==/Bookmarklet=='; let rComment = /^(\s*\/\/\s*)/; let mdKeys = metadata.keys; let mdTypes = metadata.types; let options = {}; let code = []; let errors = []; // parse file and gather options from metadata block if available data.match(/[^\r\n]+/g).forEach(function (line, i, lines) { // comment if (rComment.test(line)) { let comment = line.replace(rComment, '').trim(), canonicalComment = comment.toLowerCase().replace(/\s+/g, ''); if (!inMetadataBlock) { if (canonicalComment == openMetadata.toLowerCase()) { inMetadataBlock = true; } } else { if (canonicalComment == closeMetadata.toLowerCase()) { inMetadataBlock = false; } else { let m = comment.match(/^@([^\s]+)\s+(.*)$/); if (m) { let k = m[1]; let v = m[2]; if (k) { if (mdKeys[k] == mdTypes.list) { options[k] = options[k] || []; options[k].push(v); } else if (mdKeys[k] == mdTypes.boolean) { options[k] = v.toLowerCase() == 'true'; } else { options[k] = v; } } else { warn(`ignoring invalid metadata option: '${k}'`); } } } } // code } else { code.push(line); } if (inMetadataBlock && i + 1 == lines.length) { errors.push(`missing metdata block closing '${closeMetadata}'`); } }); return { code: code.join('\n'), options, errors: errors.length ? errors : null }; } function makeDemo(bookmarkletCode, options) { options = options || {}; const name = options.name || 'Bookmarklet'; const createdWith = 'https://github.com/mrcoles/bookmarklet'; const html = `
Drag this button to your bookmarks bar to save it as a bookmarklet:
${ options.repo ? `See source at ${options.repo}
` : '' }This page was created with the bookmarklet npm library.