├── .gitignore ├── assets ├── js.gif ├── demo.png ├── icon.png ├── icon.svg └── test.html ├── tsconfig.json ├── bsconfig.json ├── .github └── workflows │ └── test.yml ├── src ├── content.ts ├── importer.ts └── importer.test.ts ├── package.json ├── LICENSE ├── README.md └── pnpm-lock.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | .plasmo 4 | lib 5 | -------------------------------------------------------------------------------- /assets/js.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/console-importer/HEAD/assets/js.gif -------------------------------------------------------------------------------- /assets/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/console-importer/HEAD/assets/demo.png -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/console-importer/HEAD/assets/icon.png -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "strict": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /bsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "console-importer", 3 | "sources": { 4 | "dir": "src", 5 | "subdirs": true 6 | }, 7 | "package-specs": [ 8 | { 9 | "module": "es6", 10 | "in-source": true 11 | } 12 | ], 13 | "bs-dependencies": ["rescript-webapi"] 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | pull_request: 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: actions/setup-node@v3 11 | - run: corepack enable 12 | - run: pnpm install 13 | - run: pnpm test 14 | -------------------------------------------------------------------------------- /assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | $i 7 | 8 | -------------------------------------------------------------------------------- /src/content.ts: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/questions/20499994/access-window-variable-from-content-script 2 | import type { PlasmoContentScript } from "plasmo"; 3 | import importer from "url:./importer.ts"; 4 | 5 | export const config: PlasmoContentScript = { 6 | // matches: [''], 7 | all_frames: true, 8 | match_about_blank: true, 9 | run_at: "document_end", 10 | }; 11 | 12 | const script = document.createElement("script"); 13 | script.src = importer; 14 | script.type = "module"; 15 | document.body.appendChild(script); 16 | document.body.removeChild(script); 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "console-importer", 3 | "displayName": "Console Importer", 4 | "version": "2.1.0", 5 | "private": true, 6 | "description": "Import JavaScript and CSS resources from console, with one command", 7 | "homepage": "https://github.com/pd4d10/console-importer", 8 | "scripts": { 9 | "build": "plasmo build", 10 | "dev": "plasmo dev", 11 | "icon": "rm app/images/icon.png; svg2png app/images/icon.svg -o app/images/icon.png", 12 | "package": "plasmo package", 13 | "test": "vitest --dom" 14 | }, 15 | "devDependencies": { 16 | "happy-dom": "^8.1.1", 17 | "plasmo": "^0.60.2", 18 | "rescript": "^10.1.0", 19 | "rescript-webapi": "^0.7.0", 20 | "tiza": "^2.2.1", 21 | "vitest": "^0.26.2" 22 | }, 23 | "packageManager": "pnpm@7.19.0" 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Rongjian Zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 | 29 | 35 |
36 |
37 | 38 | 44 |
45 | 46 |
47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Console Importer 2 | 3 | [![Chrome Web Store](https://img.shields.io/chrome-web-store/v/hgajpakhafplebkdljleajgbpdmplhie.svg)](https://chrome.google.com/webstore/detail/console-importer/hgajpakhafplebkdljleajgbpdmplhie) 4 | [![Chrome Web Store](https://img.shields.io/chrome-web-store/d/hgajpakhafplebkdljleajgbpdmplhie.svg)](https://chrome.google.com/webstore/detail/console-importer/hgajpakhafplebkdljleajgbpdmplhie) 5 | [![Chrome Web Store](https://img.shields.io/chrome-web-store/stars/hgajpakhafplebkdljleajgbpdmplhie.svg)](https://chrome.google.com/webstore/detail/console-importer/hgajpakhafplebkdljleajgbpdmplhie) 6 | 7 | Demo 8 | 9 | ## Installation 10 | 11 | Install it from Chrome Web Store: 12 | 13 | https://chrome.google.com/webstore/detail/console-importer/hgajpakhafplebkdljleajgbpdmplhie 14 | 15 | ## Usage 16 | 17 | Open Chrome devtools console, a function named `$i` could be used to import JavaScript and CSS resources. 18 | 19 | ```js 20 | $i('jquery') 21 | ``` 22 | 23 | Import specific version: 24 | 25 | ```js 26 | $i('jquery@2') 27 | ``` 28 | 29 | Also, you can import a valid script URL: 30 | 31 | ```js 32 | $i('https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js') 33 | ``` 34 | 35 | CSS is supported, too: 36 | 37 | ```js 38 | $i('https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css') 39 | ``` 40 | 41 | ### Import ES Module 42 | 43 | ES module has been widely supported in modern browsers. `$i.esm` method can be useful in this case: 44 | 45 | ```js 46 | d3 = await $i.esm('d3') 47 | ``` 48 | 49 | or specify a version: 50 | 51 | ```js 52 | d3 = await $i.esm('d3@7') 53 | ``` 54 | 55 | The advantage of this approach is that no global variables are added to the window, which allows better control over the scope of side effects. For more details, see https://esm.run. 56 | 57 | ## Trouble shooting 58 | 59 | ### Q: `$i` doesn't work as expected 60 | 61 | Some websites like Google Inbox already have `$i` used as a global variable. This extension doesn't overwrite it. 62 | 63 | You can use `console.$i` on these websites. 64 | 65 | ### Q: `$i` fail to import resources 66 | 67 | On some websites like GitHub, `$i` will fail to import resources. Console errors may be like follows: 68 | 69 | ```sh 70 | # js errors example 71 | Refused to connect to 'https://api.cdnjs.com/libraries?search=jquery' because it violates the following Content Security Policy directive: 72 | 73 | # css errors example 74 | Refused to load the stylesheet 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' because it violates the following Content Security Policy directive: 75 | ``` 76 | 77 | It is because of strict Content Security Policy of these websites. For more information, see [Content Security Policy (CSP) wiki](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) 78 | 79 | ## How does it work? 80 | 81 | - If it is like a JavaScript lib name, like `jquery`, try to load it from cdnjs 82 | - If it has version number, like `jquery@2`, try to load it from unpkg 83 | - If it is a valid URL(CSS or JS), load it directly 84 | 85 | For advanced use, there are also two functions `$i.unpkg` and `$i.cdnjs` which could be used to import resources from specific CDN. 86 | 87 | ## License 88 | 89 | MIT 90 | -------------------------------------------------------------------------------- /src/importer.ts: -------------------------------------------------------------------------------- 1 | import tiza from "tiza"; 2 | 3 | const PREFIX_TEXT = "[$i]: "; 4 | const prefix = tiza.color("blue").text; 5 | const strong = tiza.color("blue").bold().text; 6 | const error = tiza.color("red").text; 7 | const log: typeof tiza.log = (...args) => 8 | tiza.log(prefix(PREFIX_TEXT), ...args); 9 | const logError: typeof tiza.log = (...args) => 10 | tiza.log(error(PREFIX_TEXT), ...args); 11 | 12 | let lastGlobalVariableSet: Set | null = null; 13 | 14 | function createBeforeLoad(name: string) { 15 | return () => { 16 | lastGlobalVariableSet = new Set(Object.keys(window)); 17 | log(strong(name), " is loading, please be patient..."); 18 | }; 19 | } 20 | 21 | function createOnLoad(name: string, url?: string) { 22 | return () => { 23 | const urlText = url ? `(${url})` : ""; 24 | log(strong(name), `${urlText} is loaded.`); 25 | 26 | const currentGlobalVariables = Object.keys(window); 27 | const newGlobalVariables = currentGlobalVariables.filter( 28 | (key) => !lastGlobalVariableSet?.has(key) 29 | ); 30 | if (newGlobalVariables.length > 0) { 31 | log( 32 | "The new global variables are as follows: ", 33 | strong(newGlobalVariables.join(",")), 34 | " . Maybe you can use them." 35 | ); 36 | } else { 37 | // maybe css request or script loaded already 38 | } 39 | // Update global variable list 40 | lastGlobalVariableSet = new Set(currentGlobalVariables); 41 | }; 42 | } 43 | 44 | function createOnError(name: string, url?: string) { 45 | return () => { 46 | const urlText = url ? `(${strong(url)})` : ""; 47 | logError( 48 | "Fail to load ", 49 | strong(name), 50 | ", is this URL", 51 | urlText, 52 | " correct?" 53 | ); 54 | }; 55 | } 56 | 57 | // Try to remove referrer for security 58 | // https://imququ.com/post/referrer-policy.html 59 | // https://www.w3.org/TR/referrer-policy/ 60 | function addNoReferrerMeta() { 61 | const originMeta = document.querySelector( 62 | "meta[name=referrer]" 63 | ); 64 | 65 | if (originMeta) { 66 | // If there is already a referrer policy meta tag, save origin content 67 | // and then change it, call `remove` to restore it 68 | const content = originMeta.content; 69 | originMeta.content = "no-referrer"; 70 | return function remove() { 71 | originMeta.content = content; 72 | }; 73 | } else { 74 | // Else, create a new one, call `remove` to delete it 75 | const meta = document.createElement("meta"); 76 | meta.name = "referrer"; 77 | meta.content = "no-referrer"; 78 | document.head.appendChild(meta); 79 | return function remove() { 80 | // Removing meta tag directly not work, should set it to default first 81 | meta.content = "no-referrer-when-downgrade"; 82 | document.head.removeChild(meta); 83 | }; 84 | } 85 | } 86 | 87 | // Insert script tag 88 | function injectScript( 89 | url: string, 90 | onload: ReturnType, 91 | onerror: ReturnType 92 | ) { 93 | const remove = addNoReferrerMeta(); 94 | const script = document.createElement("script"); 95 | script.src = url; 96 | script.onload = onload; 97 | script.onerror = onerror; 98 | document.body.appendChild(script); 99 | remove(); 100 | document.body.removeChild(script); 101 | } 102 | 103 | // Insert link tag 104 | function injectStyle( 105 | url: string, 106 | onload: ReturnType, 107 | onerror: ReturnType 108 | ) { 109 | const remove = addNoReferrerMeta(); 110 | const link = document.createElement("link"); 111 | link.href = url; 112 | link.rel = "stylesheet"; 113 | // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#Stylesheet_load_events 114 | link.onload = onload; 115 | link.onerror = onerror; 116 | document.head.appendChild(link); 117 | remove(); 118 | // Should not remove tag, unlike