├── .eslintrc.cjs ├── .gitignore ├── LICENSE ├── README.md ├── bin ├── README ├── interactive.js └── wright ├── examples ├── mithril │ ├── README.md │ ├── css │ │ ├── ball.css │ │ └── style.css │ ├── index.html │ ├── js │ │ ├── app.js │ │ ├── layout.js │ │ ├── models │ │ │ └── login.js │ │ ├── pages │ │ │ ├── css.js │ │ │ ├── intro.js │ │ │ ├── js.js │ │ │ └── login.js │ │ └── routes.js │ ├── package-lock.json │ └── package.json ├── rollup │ ├── css │ │ └── style.styl │ ├── js │ │ ├── app.js │ │ └── component.js │ ├── package-lock.json │ ├── package.json │ └── wright.js └── simple │ ├── README.md │ ├── css │ └── style.css │ ├── images │ └── pure.jpg │ ├── index.html │ ├── js │ └── app.js │ ├── package-lock.json │ └── package.json ├── lib ├── browser │ ├── catcher.js │ ├── index.js │ ├── inspect.js │ ├── p.js │ ├── ubre.js │ ├── wright.js │ └── wright.js.map ├── chrome │ ├── index.js │ ├── launch.js │ ├── probe.js │ ├── tabs.js │ ├── wait.js │ └── websockets.js ├── config.js ├── execute.js ├── firefox │ └── index.js ├── index.js ├── jail.js ├── log.js ├── serve │ ├── client.js │ ├── clone.js │ ├── css.js │ ├── html.js │ ├── index.js │ └── js.js ├── session.js ├── utils.js └── watch │ ├── assets.js │ ├── css.js │ ├── index.js │ ├── js.js │ └── watch.js ├── package-lock.json ├── package.json ├── rollup.config.js └── test └── data-dir.js /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: "@babel/eslint-parser", 3 | root: true, 4 | env: { 5 | es6: true, 6 | node: true, 7 | browser: true 8 | }, 9 | parserOptions: { 10 | ecmaVersion: 2021, 11 | sourceType: 'module', 12 | // babel-eslint for non transpiled files 13 | // https://github.com/babel/babel-eslint#additional-parser-configuration 14 | requireConfigFile: false 15 | }, 16 | rules: { 17 | /* Possible errors */ 18 | 'comma-dangle': 2, 19 | 'no-cond-assign': 2, 20 | 'no-console': 1, 21 | 'no-constant-condition': 2, 22 | 'no-control-regex': 2, 23 | 'no-debugger': 2, 24 | 'no-dupe-args': 2, 25 | 'no-dupe-keys': 2, 26 | 'no-duplicate-case': 2, 27 | 'no-empty': 2, 28 | 'no-empty-character-class': 2, 29 | 'no-ex-assign': 2, 30 | 'no-extra-boolean-cast': 2, 31 | 'no-extra-semi': 2, 32 | 'no-func-assign': 2, 33 | 'no-invalid-regexp': 2, 34 | 'no-irregular-whitespace': 2, 35 | 'no-negated-in-lhs': 2, 36 | 'no-obj-calls': 2, 37 | 'no-regex-spaces': 2, 38 | 'no-sparse-arrays': 2, 39 | 'no-unreachable': 2, 40 | 'use-isnan': 2, 41 | 'valid-typeof': 2, 42 | 43 | /* Best practices */ 44 | 'accessor-pairs': 2, 45 | 'array-callback-return': 0, 46 | 'consistent-return': 0, 47 | 'default-case': 2, 48 | 'dot-location': [2, 'property'], 49 | 'dot-notation': [2, { 50 | 'allowPattern': '^[a-z]+(_[a-z]+)+$' 51 | }], 52 | 'no-alert': 2, 53 | 'no-caller': 2, 54 | 'no-case-declarations': 2, 55 | 'no-empty-pattern': 0, 56 | 'no-eval': 2, 57 | 'no-extend-native': 2, 58 | 'no-extra-bind': 2, 59 | 'no-extra-label': 2, 60 | 'no-fallthrough': 2, 61 | 'no-floating-decimal': 2, 62 | 'no-implicit-globals': 2, 63 | 'no-implied-eval': 2, 64 | 'no-invalid-this': 2, 65 | 'no-iterator': 2, 66 | 'no-labels': 2, 67 | 'no-lone-blocks': 2, 68 | 'no-loop-func': 0, 69 | 'no-continue': 0, 70 | 'no-magic-numbers': 0, 71 | 'no-multi-spaces': [2, { 72 | exceptions: { 73 | Array: true, 74 | Property: true, 75 | VariableDeclarator: true, 76 | ImportDeclaration: true 77 | } 78 | }], 79 | 'no-multi-str': 2, 80 | 'no-native-reassign': 2, 81 | 'no-new': 2, 82 | 'no-new-func': 2, 83 | 'no-new-wrappers': 2, 84 | 'no-octal': 2, 85 | 'no-octal-escape': 2, 86 | 'no-param-reassign': 0, 87 | 'no-proto': 2, 88 | 'no-redeclare': 2, 89 | 'no-return-assign': 0, 90 | 'no-script-url': 2, 91 | 'no-self-assign': 2, 92 | 'no-self-compare': 2, 93 | 'no-throw-literal': 2, 94 | 'no-unmodified-loop-condition': 2, 95 | 'no-unused-expressions': 0, 96 | 'no-unused-labels': 2, 97 | 'no-useless-call': 2, 98 | 'no-useless-concat': 2, 99 | 'no-useless-escape': 2, 100 | 'no-void': 2, 101 | 'no-with': 2, 102 | 'wrap-iife': 2, 103 | 104 | /* Variables*/ 105 | 'no-delete-var': 2, 106 | 'no-label-var': 2, 107 | 'no-restricted-globals': 2, 108 | 'no-shadow': 0, 109 | 'no-shadow-restricted-names': 2, 110 | 'no-undef': 2, 111 | 'no-undef-init': 2, 112 | 'no-unused-vars': [1, { 113 | 'vars': 'all', 114 | 'args': 'none' 115 | }], 116 | 'no-use-before-define': [2, { 117 | "functions": false 118 | }], 119 | 120 | /* Node.js and CommonJS */ 121 | 'callback-return': 0, 122 | 'global-require': 2, 123 | 'handle-callback-err': 2, 124 | 'no-mixed-requires': 2, 125 | 'no-new-require': 2, 126 | 'no-path-concat': 2, 127 | 'no-process-exit': 0, 128 | 129 | /* Stylistic issues */ 130 | 'block-spacing': [2, 'always'], 131 | 'comma-spacing': 2, 132 | 'comma-style': [2, 'first', { 133 | exceptions: { 134 | ArrayExpression: true, 135 | ObjectExpression: true 136 | } 137 | }], 138 | 'consistent-this': [2, 'self'], 139 | 'eol-last': 2, 140 | 'key-spacing': [0, { 141 | beforeColon: false, 142 | afterColon: true, 143 | mode: 'minimum' 144 | }], 145 | 'keyword-spacing': 2, 146 | 'linebreak-style': 2, 147 | 'lines-around-comment': 2, 148 | 'max-depth': [2, 5], 149 | 'max-len': [0, 150], 150 | 'max-params': [2, 5], 151 | 'max-statements-per-line': 2, 152 | 'new-cap': [2, { 153 | capIsNew: false 154 | }], 155 | 'new-parens': 2, 156 | 'newline-after-var': 0, 157 | 'newline-before-return': 0, 158 | 'no-array-constructor': 2, 159 | 'no-bitwise': 0, 160 | 'no-lonely-if': 2, 161 | 'no-mixed-spaces-and-tabs': 2, 162 | 'no-new-object': 2, 163 | // 'no-spaced-func': 2, 164 | 'no-trailing-spaces': 1, 165 | 'no-unneeded-ternary': 2, 166 | 'no-whitespace-before-property': 2, 167 | 'object-curly-spacing': [2, 'always'], 168 | 'one-var-declaration-per-line': [2, 'always'], 169 | 'quote-props': [2, 'as-needed'], 170 | 'semi': [2, 'never'], 171 | 'space-before-blocks': 2, 172 | 'space-before-function-paren': [2, 'never'], 173 | 'space-unary-ops': 2, 174 | 'spaced-comment': 2, 175 | 176 | /* ES6 */ 177 | 'arrow-spacing': 2, 178 | 'constructor-super': 2, 179 | 'no-class-assign': 2, 180 | 'no-confusing-arrow': 0, 181 | 'no-const-assign': 2, 182 | 'no-dupe-class-members': 2, 183 | 'no-duplicate-imports': 2, 184 | 'no-new-symbol': 2, 185 | 'no-this-before-super': 2, 186 | 'no-useless-constructor': 2, 187 | 'no-var': 2, 188 | 'object-shorthand': 0, 189 | 'prefer-arrow-callback': 0, 190 | 'prefer-rest-params': 0, 191 | 'prefer-spread': 0 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | logs 4 | *.log 5 | npm-debug.log* 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2019 Rasmus Porsager 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 Wright 2 | 3 | #### Hot Module Reloading: at the *Virtual Machine* level 4 | 5 | - Compatible with any framework (or no framework at all) 6 | - Patches your JS, CSS and Static Assets on change without losing app state 7 | - Uses the Chrome Debugger Protocol to replace the source files in the VM 8 | - Convenient CLI/JS API for lightweight build systems / npm scripts 9 | - Fallback to regular refresh in all other browsers 10 | 11 | ## Quick start 12 | 13 | Wright was built to be able to plugin to various developer setups, and due to that it will have to be set up in many different ways. The quickest way to start is to open a terminal, cd to your project directory. 14 | 15 | Then start wright by pointing either to your main .html entry point 16 | ``` 17 | wright index.html 18 | ``` 19 | 20 | or a url if you already have a dev server. 21 | ``` 22 | wright http://localhost:3000 23 | ``` 24 | 25 | From there on you can add extra options to benefit from wrights complete functionality. 26 | 27 | ## Framework Support 28 | 29 | Wright is framework agnostic because the code patching happens at the VM level. 30 | 31 | Wright has been tested with many frameworks, and there are plenty of examples to help get you started. 32 | 33 | Tested successfully with: 34 | 35 | - [VanillaJs](https://github.com/porsager/Wright/tree/master/examples/simple) 36 | - React 37 | - [Mithril](https://github.com/porsager/Wright/tree/master/examples/mithril) 38 | - [Rollup](https://github.com/porsager/Wright/tree/master/examples/rollup) 39 | - [Browserify](https://github.com/porsager/Wright/tree/master/examples/mithril) 40 | - Webpack 41 | - [Stylus](https://github.com/porsager/Wright/tree/master/examples/mithril) 42 | - postcss 43 | 44 | And should also work awesomely with candylize, fragmentor, pistontulastic or anything else you can throw at it :thumbsup: 45 | 46 | Currently works with Chrome, tested on OSX & Windows 10. 47 | 48 | Wright automatically watches any resource (js, css, image, font) that is loaded in the browser, and hot reloads it when it changes. 49 | 50 | ## Getting started 51 | 52 | Wright can start a server for you using a specified index.html file or a boilerplate html where your js & css will be injected into. 53 | 54 | It can also take a url to an existing server and inject js & css in to that. 55 | 56 | ## CLI API 57 | ``` 58 | $ npm install -g wright 59 | ``` 60 | Using wright as a cli makes it easy to get started right away or to set up your project with npm scripts. 61 | 62 | ## Options 63 | ``` 64 | main Specifies the entry point of your app. Point to a .html 65 | file to start a server serving that at all directory paths. 66 | If you already have you own server running specifiy the 67 | full url like http://localhost:5000 68 | 69 | Standard Options: 70 | 71 | -r, --run Activates Hot module reloading. This will inject any changed 72 | javascript files, and then run the function if supplied. 73 | If this is not specified, changing javascript 74 | files will cause a full refresh. 75 | 76 | -s, --serve Specify which local directory that is being served. 77 | Defaults to folder of main file or the current directory. 78 | 79 | -w, --watch Any folder, file or file type to watch that should cause a 80 | refresh of the browser. Use commas to add more. 81 | 82 | --js 83 | 84 | --css 85 | ``` 86 | 87 | #### Example 88 | ``` 89 | $ wright dist/index.html -r "m.redraw" 90 | ``` 91 | 92 | ## Javascript API 93 | 94 | Using wright with javascript is great if you have some build steps or compilation that needs to happen before hot reloading js or css. 95 | It also allows you to avoid touching the file system, thereby getting a quicker time to screen for your js & css changes. 96 | 97 | ``` 98 | $ npm install -D wright 99 | ``` 100 | 101 | Wright exports a function which takes the options used for launching. 102 | 103 | ```js 104 | const wright = require('wright') 105 | 106 | wright({ 107 | // Main should specify the entry point of your app. 108 | // .html (eg. index.html) 109 | // url (eg. http://localhost:5000) 110 | // defaults to using a boilerplate html file. 111 | main : 'src/index.html', 112 | 113 | // Specify which directory to serve. This is the directory 114 | // where wright will watch the files loaded in the browser. 115 | // Defaults to root of main html file or CWD. 116 | serve : String, // Path to directory to serve 117 | // Defaults to root of main 118 | // html file or CWD. 119 | 120 | // Activates Hot module reloading. This will inject any 121 | // changed javascript files, and then run the global function 122 | // with the changed path as argument { path: 'file.js' }. 123 | run : String, // Global function to call on change 124 | 125 | // The JS property dynamically injects scripts without 126 | // touching the file system. You can add your build scripts 127 | // here to do their thing when your source changes. 128 | // Remember you just need to target chrome, so any build 129 | // steps including ES6>ES5 transpiling or minification is 130 | // unnecessary overhead. 131 | js : { 132 | compile : Function, // A function that returns a Promise 133 | // resolving to the source code, or a 134 | // function with a callback argument called 135 | // in node style callback(err, code). 136 | // * Required 137 | 138 | path : String, // Path to use as script src 139 | // Defaults to wrightinjected.js 140 | 141 | watch : String, // Glob pattern used to watch files 142 | // that should trigger compilation. 143 | // Defaults to common js extensions 144 | // '**/*.{js,ls,purs,ts,cljs,coffee,litcoffee,jsx}' 145 | }, 146 | 147 | // The css property is also very useful to build and inject 148 | // css directly without touching the file system. 149 | css : { 150 | compile : Function, // A function that returns a Promise 151 | // resolving to the source code, or a 152 | // function with a callback argument called 153 | // in node style callback(err, code). 154 | // * Required 155 | 156 | path : String, // Path to use as script src 157 | // Defaults to wrightinjected.js 158 | 159 | watch : String, // Glob pattern used to watch files 160 | // that should trigger compilation. 161 | // Defaults to common js extensions 162 | // '**/*.{css,styl,less,sass}' 163 | }, 164 | 165 | // Execute can be used to start another running process. 166 | // This can be a build command with watch capabilites like 167 | // `rollup -c --watch` or backend server api the app needs 168 | // to talk to. 169 | execute : String, // A single or multiple commands 170 | 171 | // Watch is only to be used in case you want a quick way to force 172 | // a full browser refresh when some files change. This might be 173 | // useful in the case of a php server serving static html that 174 | // you want to see on file changes 175 | watch : String // Glob pattern 176 | }) 177 | ``` 178 | 179 | ## Advanced 180 | 181 | The cli or options object also takes the following more advanced options: 182 | 183 | **port** (default 3000) 184 | The port to serve from - Wright will use this as a starting point when probing for available ports. 185 | 186 | **debug** (default false) 187 | Set to true to receive debugging info in the console. If set to 2 output from chrome will also be shown. 188 | 189 | **fps** (default false) 190 | Activate the chrome fps gui widget 191 | 192 | **jail** (default true) 193 | This will jail variables in your script to stop chrome from dereferencing them while doing Hot module reloading. Defaults to true. 194 | -------------------------------------------------------------------------------- /bin/README: -------------------------------------------------------------------------------- 1 | wright version ${version} 2 | ===================================== 3 | 4 | Usage: wright main {OPTIONS} 5 | 6 | main Specifies the entry point of your app. Point to a .html 7 | file to start a server serving that at all directory paths. 8 | If you already have you own server running specifiy the 9 | full url like http://localhost:5000 10 | 11 | Standard Options: 12 | 13 | -r, --run Activates Hot module reloading. This will inject any changed 14 | javascript files, and then run the function if supplied. 15 | If this is not specified, changing javascript 16 | files will cause a full refresh. 17 | 18 | -s, --serve Specify which local directory that is being served. 19 | Defaults to folder of main file or the current directory. 20 | 21 | -w, --watch Any folder, file or file type to watch that should cause a 22 | refresh of the browser. Use commas to add more. 23 | 24 | Advanced Options: 25 | 26 | -f, --fps Activate the chrome fps counter 27 | -d, --debug Enable debug logging 28 | -n, --name Name to use for chrome profile - defaults to cwd name 29 | -j, --jail Toggles jailing of js code - defaults to true 30 | -c, --clone Will copy any resources loaded by chrome to the cwd 31 | -p, --port Start probing at this port for serving 32 | -v, --version Output the version 33 | 34 | -e, --execute Allows running another task like 'npm run build' 35 | Formatted like 'command;watch' 36 | watch is optional and will restart the command if triggered 37 | 38 | --js build command that outputs js to stdout 39 | Formated like 'command;path;watch' 40 | path and watch are optional and can be ignored with a _ 41 | 42 | --css build command that outputs css to stdout 43 | Formated like 'command;path;watch' 44 | path and watch are optional and can be ignored with a _ 45 | 46 | For more information visit - https://github.com/porsager/wright 47 | -------------------------------------------------------------------------------- /bin/interactive.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0 */ 2 | 3 | const fs = require('fs') 4 | , path = require('path') 5 | , inquirer = require('inquirer') 6 | 7 | const allowedMain = { '.html': 1, '.js': 1 } 8 | , basename = path.basename(process.cwd()) 9 | , defaultJs = '.js .ts .ls' 10 | , defaultCss = '.css .sass .styl' 11 | 12 | console.log(` 13 | 14 | _/ _/ _/_/_/ _/_/_/ _/_/_/ _/ _/ _/_/_/_/_/ 15 | _/ _/ _/ _/ _/ _/ _/ _/ _/ 16 | _/ _/ _/ _/_/_/ _/ _/ _/_/ _/_/_/_/ _/ 17 | _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ 18 | _/ _/ _/ _/ _/_/_/ _/_/_/ _/ _/ _/ 19 | 20 | Run all your development with a best case live reload setup. 21 | 22 | `) 23 | 24 | const questions = [ 25 | { 26 | type: 'list', 27 | name: 'main', 28 | message: 'What would you like to work on?', 29 | choices: [ 30 | { name: 'A served site (eg. http://localhost:3000)', short: 'url', value: 'url' }, 31 | { name: 'Local files (eg. index.html)', short: 'local', value: 'local' } 32 | ] 33 | }, { 34 | type: 'input', 35 | name: 'main', 36 | message: 'Enter url:', 37 | when: a => a.main === 'url', 38 | filter: v => v.startsWith('http://') ? v : ('http://' + v) 39 | }, { 40 | type: 'input', 41 | name: 'main', 42 | message: 'Enter path:', 43 | default: a => fs.existsSync(path.join(process.cwd(), 'index.html')) ? 'index.html' : '', 44 | when: a => a.main === 'local', 45 | validate: v => allowedMain[path.extname(v)] 46 | ? fs.existsSync(v) 47 | ? true 48 | : 'File not found' 49 | : 'Only .html and .js files allowed' 50 | }, { 51 | type: 'input', 52 | name: 'serve', 53 | default: './', 54 | message: 'Path for files served:', 55 | basePath: '.' 56 | }, { 57 | type: 'confirm', 58 | name: 'css', 59 | default: false, 60 | message: 'Run a css build script on changes?' 61 | }, { 62 | type: 'input', 63 | name: 'css.compile', 64 | default: 'npm run build:css', 65 | message: 'Command:', 66 | when: a => a.css 67 | }, { 68 | type: 'input', 69 | name: 'css.path', 70 | message: 'Path in link href:', 71 | default: '/css/style.css', 72 | when: a => a.css 73 | }, { 74 | type: 'input', 75 | name: 'css.watch', 76 | default: defaultCss, 77 | message: 'Files to watch:', 78 | filter: v => v === defaultCss ? null : v, 79 | when: a => a.css 80 | }, { 81 | type: 'confirm', 82 | name: 'js', 83 | default: false, 84 | message: 'Run a js build script on changes?' 85 | }, { 86 | type: 'input', 87 | name: 'js.compile', 88 | message: 'Command:', 89 | default: 'npm run build:js', 90 | when: a => a.js 91 | }, { 92 | type: 'input', 93 | name: 'js.path', 94 | message: 'Path in script src:', 95 | default: '/js/app.js', 96 | when: a => a.js 97 | }, { 98 | type: 'input', 99 | name: 'js.watch', 100 | default: defaultJs, 101 | message: 'Files to watch:', 102 | filter: v => v === defaultJs ? null : v, 103 | when: a => a.js 104 | }, { 105 | type: 'confirm', 106 | name: 'run', 107 | default: false, 108 | message: 'Activate live replacement of Javascript functions (HMR)' 109 | }, { 110 | type: 'input', 111 | name: 'run', 112 | default: 'run', 113 | message: 'Enter global function reference to run (eg m.redraw)', 114 | filter: value => value.replace('()', ''), 115 | when: a => a.run 116 | }, { 117 | type: 'confirm', 118 | name: 'watch', 119 | default: false, 120 | message: 'Full reload on specific file changes? (eg .php, .html, .jade)' 121 | }, { 122 | type: 'input', 123 | name: 'watch', 124 | message: 'Files to watch:', 125 | when: a => a.watch 126 | }, { 127 | type: 'confirm', 128 | name: 'execute', 129 | message: 'Execute a command at start or on file changes?', 130 | default: false, 131 | filter: v => v ? {} : false 132 | }, { 133 | type: 'input', 134 | name: 'execute.command', 135 | message: 'Command:', 136 | when: a => a.execute 137 | }, { 138 | type: 'confirm', 139 | name: 'execute.watch', 140 | message: 'Rerun the command on file changes?', 141 | when: a => a.execute 142 | }, { 143 | type: 'input', 144 | name: 'execute.watch', 145 | message: 'Files to watch:', 146 | when: a => a.execute.watch 147 | }, { 148 | type: 'input', 149 | name: 'name', 150 | default: () => basename, 151 | message: 'Enter a unique name for this project:' 152 | }, { 153 | type: 'confirm', 154 | name: 'debug', 155 | default: true, 156 | message: 'Would you like extra debugging output?' 157 | }, { 158 | type: 'list', 159 | name: 'output', 160 | message: 'For future use Wright has a CLI and JS API, which one would you like\n' + 161 | ' to generate the command/script for?', 162 | choices: [ 163 | { name: 'CLI (lean & easy - good for npm scripts)', short: 'cli', value: 'cli' }, 164 | { name: 'JS (allows for streaming builds into the browser)', short: 'js', value: 'js' }, 165 | { name: 'Both', short: 'both', value: 'both' } 166 | ] 167 | }, { 168 | type: 'confirm', 169 | name: 'pkgjson', 170 | message: 'Add CLI command to package.json scripts', 171 | when: (a) => a.output !== 'js' && fs.existsSync(path.join(process.cwd(), 'package.json')) 172 | }, { 173 | type: 'input', 174 | name: 'pkgjson', 175 | message: 'Name for script', 176 | default: 'wright', 177 | when: (a) => a.pkgjson 178 | }, { 179 | type: 'input', 180 | name: 'jsPath', 181 | when: answers => answers.output !== 'cli', 182 | message: 'Name of js file to save', 183 | default: 'wright.js' 184 | }, { 185 | type: 'confirm', 186 | name: 'start', 187 | message: 'Would you like to run wright now?', 188 | default: false 189 | } 190 | ] 191 | 192 | inquirer.prompt(questions).then(a => { 193 | const command = createCommand(a) 194 | 195 | if (a.jsPath) 196 | fs.writeFileSync(path.isAbsolute(a.jsPath) ? a.jsPath : path.join(process.cwd(), a.jsPath), createScript(a)) 197 | 198 | if (a.output !== 'js') { 199 | 200 | if (a.pkgjson) { 201 | const pkgPath = path.join(process.cwd(), 'package.json') // eslint-disable-line 202 | , pkg = require(pkgPath) // eslint-disable-line 203 | , indentation = fs.readFileSync(pkgPath, 'utf8').replace(/\r\n/g, '\n').split('\n')[1].search(/[^\s\\]/) 204 | 205 | pkg.scripts.wright = command 206 | fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, indentation)) 207 | } 208 | 209 | console.log('\nRun this command to start wright:') 210 | console.log(command + '\n') 211 | 212 | } 213 | 214 | console.log('Thanks for setting up wright.') 215 | console.log('I hope you enjoy using it!') 216 | 217 | if (a.start) { 218 | if (a.jsPath) 219 | return require(a.jsPath) // eslint-disable-line 220 | 221 | require('child_process').execSync(command, { stdio:[0, 1, 2] }) // eslint-disable-line 222 | } 223 | }).catch(err => console.error(err)) 224 | 225 | function createScript(a) { 226 | return `const wright = require('wright') 227 | 228 | wright({ 229 | main: '${ a.main }', 230 | debug: ${ String(a.debug) }, 231 | serve: '${ String(a.serve) }', 232 | run: '${ a.run }', 233 | 234 | ${ a.css ? '' : ' /* See more in the docs' } 235 | css: [{ 236 | compile: '${ a.css ? a.css.compile : '' }', 237 | path: '${ a.css ? a.css.path : '' }', 238 | watch: '${ a.css ? a.css.watch : '' }' 239 | }], 240 | ${ a.css ? '' : ' */'} 241 | ${ a.js ? '' : ' /* See more in the docs'} 242 | js: [{ 243 | compile: '${ a.js ? a.js.compile : '' }', 244 | path: '${ a.js ? a.js.path : '' }', 245 | watch: '${ a.js ? a.js.watch : '' }' 246 | }], 247 | ${ a.js ? '' : ' */'} 248 | ${ a.execute ? '' : ' /* See more in the docs' } 249 | execute: [{ 250 | command: ${ a.execute.command }, 251 | watch: ${ a.execute.watch } 252 | }] 253 | ${ a.execute ? '' : ' */' } 254 | }) 255 | ` 256 | } 257 | 258 | function createCommand(a) { 259 | return ['wright', 260 | a.main, 261 | a.name && a.name !== basename && ('--name ' + a.name), 262 | a.serve && a.serve !== './' && ('--serve ' + a.serve), 263 | a.css && ('--css ' + getCommandString(a.css)), 264 | a.js && ('--js ' + getCommandString(a.js)), 265 | a.run && ('--run ' + a.run), 266 | a.execute && ('--execute ' + getExecuteString(a.execute)), 267 | a.debug && ('--debug 1') 268 | ].filter(n => n).join(' ') 269 | } 270 | 271 | function getCommandString(obj) { 272 | return '\'' + obj.command + ';' + (obj.path || '_') + ';' + (obj.watch || '_') + '\'' 273 | } 274 | 275 | function getExecuteString(obj) { 276 | return '\'' + obj.command + ';' + (obj.watch || '') + '\'' 277 | } 278 | -------------------------------------------------------------------------------- /bin/wright: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | const minimist = require('minimist') 4 | , fs = require('fs') 5 | , path = require('path') 6 | , wright = require('../lib/') 7 | , pkg = require('../package.json') 8 | , utils = require('../lib/utils') 9 | 10 | const command = minimist(process.argv.slice(2), { 11 | string: ['js', 'css'], 12 | default: { 13 | 'jail': true, 14 | 'assets': [] 15 | }, 16 | alias: { 17 | h: 'help', 18 | b: 'browser', 19 | v: 'version', 20 | e: 'execute', 21 | r: 'run', 22 | s: 'serve', 23 | a: 'assets', 24 | p: 'port', 25 | d: 'debug', 26 | f: 'fps', 27 | w: 'watch', 28 | j: 'jail', 29 | c: 'clone', 30 | n: 'name' 31 | }, 32 | boolean: ['fps', 'jail'] 33 | }) 34 | 35 | if (command._.length === 0) 36 | return require('./interactive') 37 | 38 | command.main = command._[0] 39 | 40 | const help = command.help || command.main === 'help' 41 | const version = command.version || command.main === 'version' 42 | 43 | if (help) 44 | return console.log(fs.readFileSync(path.join(__dirname, 'README'), 'utf8').replace('${version}', pkg.version)) 45 | else if (version) 46 | return console.log('wright version ' + pkg.version) 47 | 48 | wright({ 49 | main : command.main, 50 | run : command.run, 51 | browser : command.browser || 'chrome', 52 | js : command.js ? parseInjections(command.js) : null, 53 | css : command.css ? parseInjections(command.css) : null, 54 | execute : command.execute ? parseExecutes(command.execute) : null, 55 | env : command.env, 56 | name : command.name, 57 | jail : command.jail, 58 | fps : command.fps, 59 | port : command.port, 60 | serve : command.serve, 61 | assets : command.assets, 62 | clone : command.clone, 63 | debug : command.debug, 64 | watch : command.watch, 65 | watchThrottle: command['watch-throttle'] 66 | }).catch(err => console.error(err)) 67 | 68 | function parseInjections(str) { 69 | return (Array.isArray(str) ? str : [str]).map(part => ({ 70 | compile: part.split(';')[0], 71 | path: part.split(';')[1] === '_' ? null : part.split(';')[1], 72 | watch: part.split(';')[2] === '_' ? null : part.split(';')[2], 73 | })) 74 | } 75 | 76 | function parseExecutes(str) { 77 | return (Array.isArray(str) ? str : [str]).map(part => ({ 78 | command: part.split(';')[0], 79 | watch: part.split(';')[1] 80 | })) 81 | } 82 | -------------------------------------------------------------------------------- /examples/mithril/README.md: -------------------------------------------------------------------------------- 1 | # Mithril & Wright Example 2 | 3 | A better readme will be coming, but for now you can test it out by running the following: 4 | 5 | from the root directory: 6 | ``` 7 | npm install 8 | ``` 9 | 10 | from this directory: 11 | ``` 12 | npm install 13 | wright index.html 14 | ``` 15 | 16 | To see the result try and go change the js and css files. 17 | -------------------------------------------------------------------------------- /examples/mithril/css/ball.css: -------------------------------------------------------------------------------- 1 | @keyframes roll { 2 | 0% { transform: translateX(-200px); } 3 | 50% { transform: translateX(200px); } 4 | 100% { transform: translateX(-200px); } 5 | } 6 | 7 | .ball { 8 | display: inline-block; 9 | width: 40px; 10 | height: 40px; 11 | background: red; 12 | border-radius: 20px; 13 | animation: roll 3s infinite; 14 | animation-fill-mode: forwards; 15 | } 16 | -------------------------------------------------------------------------------- /examples/mithril/css/style.css: -------------------------------------------------------------------------------- 1 | @import url('/css/ball.css'); 2 | 3 | html { 4 | box-sizing: border-box; 5 | min-height: 100%; 6 | } 7 | *, *:before, *:after { 8 | box-sizing: inherit; 9 | } 10 | 11 | body { 12 | background: white; 13 | color: #333; 14 | margin: 0px; 15 | min-height: 100%; 16 | font-family: sans-serif; 17 | text-align: center; 18 | } 19 | 20 | h1, h2, h3 { 21 | font-weight: normal; 22 | } 23 | 24 | header { 25 | position: relative; 26 | background: #2a2a2a; 27 | } 28 | 29 | header button { 30 | position: absolute; 31 | top: 10px; 32 | right: 10px; 33 | } 34 | 35 | p { 36 | max-width: 500px; 37 | margin: 0 auto; 38 | line-height: 1.75em; 39 | } 40 | 41 | header nav a { 42 | display: inline-block; 43 | padding: 18px 20px 14px 20px; 44 | margin: 0 3px; 45 | text-transform: uppercase; 46 | border-bottom: 4px solid #555; 47 | cursor: pointer; 48 | text-decoration: none; 49 | color: gray; 50 | } 51 | 52 | header nav a:hover, header nav a.active { 53 | color: white; 54 | border-bottom: 4px solid white; 55 | } 56 | 57 | main { 58 | padding: 20px; 59 | } 60 | 61 | #login { 62 | width: 320px; 63 | margin: 50px auto; 64 | text-align: center; 65 | } 66 | 67 | #login input { 68 | display: block; 69 | margin: 10px; 70 | width: 300px; 71 | } 72 | 73 | input, button { 74 | padding: 5px; 75 | border: 2px solid #ddd; 76 | font-size: 16px; 77 | } 78 | 79 | header button { 80 | float: right; 81 | margin: 0; 82 | } 83 | -------------------------------------------------------------------------------- /examples/mithril/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mithril Wright Example 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/mithril/js/app.js: -------------------------------------------------------------------------------- 1 | import m from 'mithril' 2 | import routes from './routes.js' 3 | import login from './models/login.js' 4 | 5 | window.m = m 6 | 7 | let checkLogin = () => { 8 | if (!login.user) { 9 | console.warn('Unauthorized') 10 | if (window.location.pathname !== '/login') 11 | login.redirect = window.location.pathname + window.location.search 12 | window.history.pushState(null, null, '/login') 13 | } 14 | } 15 | 16 | window.onhashchange = () => checkLogin() 17 | 18 | window.onload = () => { 19 | m.route(document.body, '/404', routes) 20 | checkLogin() 21 | } 22 | -------------------------------------------------------------------------------- /examples/mithril/js/layout.js: -------------------------------------------------------------------------------- 1 | import m from 'mithril' 2 | 3 | const layout = { 4 | view: (vnode) => { 5 | const menus = [ 6 | { href: '/', title: 'intro' }, 7 | { href: '/js', title: 'js' }, 8 | { href: '/css', title: 'css' }, 9 | { href: '/login', title: 'logout' } 10 | ] 11 | 12 | return [ 13 | m('header', [ 14 | m('iframe', { 15 | width: 420, 16 | height: 315, 17 | src: 'https://www.youtube.com/embed/oHg5SJYRHA0?html5=1&autoplay=1&showinfo=0&controls=0', 18 | frameborder: 0, 19 | allowfullscreen: true 20 | }), 21 | m('nav', menus.map(menu => 22 | m('a', { 23 | href: menu.href, 24 | class: m.route.get() === menu.href ? 'active' : '', 25 | oncreate: m.route.link 26 | }, menu.title) 27 | )) 28 | ]), 29 | m('main', vnode.children) 30 | ] 31 | } 32 | } 33 | 34 | export default function(page) { 35 | return { render: () => m(layout, m(page)) } 36 | } 37 | -------------------------------------------------------------------------------- /examples/mithril/js/models/login.js: -------------------------------------------------------------------------------- 1 | import m from 'mithril' 2 | 3 | const login = { 4 | user: '', 5 | redirect: '/', 6 | logout: () => { 7 | login.user = null 8 | login.redirect = '/' 9 | m.route.set('/login') 10 | }, 11 | submit: ({ username, password }) => { 12 | login.user = username === 'test' && password === 'test' && username 13 | login.error = !login.user && 'Wrong login - Try test/test' 14 | 15 | if (login.user) 16 | m.route.set(login.redirect) 17 | } 18 | } 19 | 20 | export default login 21 | -------------------------------------------------------------------------------- /examples/mithril/js/pages/css.js: -------------------------------------------------------------------------------- 1 | import m from 'mithril' 2 | 3 | export default { 4 | view: () => [ 5 | m('h1', 'css'), 6 | m('p', 'So this ball is moving a bit too fast, and it is a bit too red as well. Try to edit examples/mithril/css/ball.css'), 7 | m('.ball') 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /examples/mithril/js/pages/intro.js: -------------------------------------------------------------------------------- 1 | import m from 'mithril' 2 | 3 | const heading = { 4 | view: vnode => m('h1', vnode.attrs.content ) 5 | } 6 | 7 | const paragraph = { 8 | view: vnode => m('p', vnode.attrs.content ) 9 | } 10 | 11 | export default { 12 | view: () => [ 13 | m(heading, { content: 'Introduction' }), 14 | m(paragraph, { content: 'So while you\'re listening to that great song you should go ahead and edit the files inside examples/mithril' }) 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /examples/mithril/js/pages/js.js: -------------------------------------------------------------------------------- 1 | import m from 'mithril' 2 | 3 | export default { 4 | oninit: vnode => { 5 | vnode.state.start = Date.now() 6 | vnode.state.interval = setInterval(() => m.redraw(), 1000) 7 | }, 8 | onremove: vnode => { 9 | clearInterval(vnode.state.interval) 10 | }, 11 | view: vnode => [ 12 | m('h1', 'js'), 13 | m('p', 'Thisexample shows how keeping the state in the browser is possible while doing hot reloading of anything'), 14 | m('p', 'ms: ' + (Date.now() - vnode.state.start)) 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /examples/mithril/js/pages/login.js: -------------------------------------------------------------------------------- 1 | import m from 'mithril' 2 | import login from '../models/login.js' 3 | 4 | export default { 5 | oninit: ({ state }) => { 6 | state.username = login.user || '' 7 | state.password = login.password || '' 8 | }, 9 | 10 | view: ({ state }) => m('form#login', [ 11 | m('h1', 'Login'), 12 | m('p', login.error || 'Try using test/test'), 13 | m('form', { 14 | onsubmit: e => { 15 | e.preventDefault() 16 | login.submit(state) 17 | } 18 | }, [ 19 | m('input', { 20 | value: login.user || state.username, 21 | placeholder: 'username', 22 | onchange: e => state.username = e.target.value 23 | }), 24 | m('input[type=password]', { 25 | placeholder: 'password', 26 | onchange: e => state.password = e.target.value 27 | }), 28 | m('input[type=submit]') 29 | ]) 30 | ]) 31 | } 32 | -------------------------------------------------------------------------------- /examples/mithril/js/routes.js: -------------------------------------------------------------------------------- 1 | import m from 'mithril' 2 | import l from './layout.js' 3 | import login from './pages/login.js' 4 | import intro from './pages/intro.js' 5 | import js from './pages/js.js' 6 | import css from './pages/css.js' 7 | 8 | m.route.prefix = '' 9 | 10 | const routes = { 11 | '/login' : login, 12 | '/' : l(intro), 13 | '/js' : l(js), 14 | '/css' : l(css), 15 | '/404' : l({ view: () => m('h1', 'not found') }) 16 | } 17 | 18 | export default routes 19 | -------------------------------------------------------------------------------- /examples/mithril/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wright-example-mithril", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "wright-example-mithril", 9 | "version": "1.0.0", 10 | "dependencies": { 11 | "mithril": "github:porsager/mithril.js#esm-dev" 12 | } 13 | }, 14 | "node_modules/mithril": { 15 | "version": "2.0.4", 16 | "resolved": "git+ssh://git@github.com/porsager/mithril.js.git#11cd300c60823fbee5d3bc30acb9b40dc85923a1", 17 | "license": "MIT", 18 | "bin": { 19 | "ospec": "ospec/bin/ospec" 20 | } 21 | } 22 | }, 23 | "dependencies": { 24 | "mithril": { 25 | "version": "git+ssh://git@github.com/porsager/mithril.js.git#11cd300c60823fbee5d3bc30acb9b40dc85923a1", 26 | "from": "mithril@github:porsager/mithril.js#esm-dev" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/mithril/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wright-example-mithril", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "wright": "../../bin/wright -r m.redraw index.html" 6 | }, 7 | "dependencies": { 8 | "mithril": "github:porsager/mithril.js#esm-dev" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/rollup/css/style.styl: -------------------------------------------------------------------------------- 1 | body 2 | background white 3 | margin 50px 4 | font-family sans-serif 5 | text-align center 6 | font-size 20px 7 | 8 | -------------------------------------------------------------------------------- /examples/rollup/js/app.js: -------------------------------------------------------------------------------- 1 | import reload from './component.js' 2 | 3 | window.reload = reload 4 | 5 | window.onload = function() { 6 | document.body.innerText = 'Refreshed at ' + new Date() 7 | } 8 | -------------------------------------------------------------------------------- /examples/rollup/js/component.js: -------------------------------------------------------------------------------- 1 | export default function reload() { 2 | document.body.innerText = 'File hot reloaded at ' + Date.now() 3 | } 4 | -------------------------------------------------------------------------------- /examples/rollup/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wright-example-rollup", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "wright-example-rollup", 9 | "version": "1.0.0", 10 | "dependencies": { 11 | "rollup": "2.26.11" 12 | }, 13 | "devDependencies": { 14 | "stylus": "0.54.8" 15 | } 16 | }, 17 | "node_modules/atob": { 18 | "version": "2.1.2", 19 | "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", 20 | "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", 21 | "dev": true, 22 | "bin": { 23 | "atob": "bin/atob.js" 24 | }, 25 | "engines": { 26 | "node": ">= 4.5.0" 27 | } 28 | }, 29 | "node_modules/balanced-match": { 30 | "version": "1.0.2", 31 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 32 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 33 | "dev": true 34 | }, 35 | "node_modules/brace-expansion": { 36 | "version": "1.1.11", 37 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 38 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 39 | "dev": true, 40 | "dependencies": { 41 | "balanced-match": "^1.0.0", 42 | "concat-map": "0.0.1" 43 | } 44 | }, 45 | "node_modules/concat-map": { 46 | "version": "0.0.1", 47 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 48 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 49 | "dev": true 50 | }, 51 | "node_modules/css": { 52 | "version": "2.2.4", 53 | "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", 54 | "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", 55 | "dev": true, 56 | "dependencies": { 57 | "inherits": "^2.0.3", 58 | "source-map": "^0.6.1", 59 | "source-map-resolve": "^0.5.2", 60 | "urix": "^0.1.0" 61 | } 62 | }, 63 | "node_modules/css-parse": { 64 | "version": "2.0.0", 65 | "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz", 66 | "integrity": "sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=", 67 | "dev": true, 68 | "dependencies": { 69 | "css": "^2.0.0" 70 | } 71 | }, 72 | "node_modules/css/node_modules/source-map": { 73 | "version": "0.6.1", 74 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 75 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 76 | "dev": true, 77 | "engines": { 78 | "node": ">=0.10.0" 79 | } 80 | }, 81 | "node_modules/debug": { 82 | "version": "3.1.0", 83 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 84 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 85 | "dev": true, 86 | "dependencies": { 87 | "ms": "2.0.0" 88 | } 89 | }, 90 | "node_modules/decode-uri-component": { 91 | "version": "0.2.0", 92 | "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", 93 | "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", 94 | "dev": true, 95 | "engines": { 96 | "node": ">=0.10" 97 | } 98 | }, 99 | "node_modules/fs.realpath": { 100 | "version": "1.0.0", 101 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 102 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 103 | "dev": true 104 | }, 105 | "node_modules/fsevents": { 106 | "version": "2.1.3", 107 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", 108 | "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", 109 | "deprecated": "\"Please update to latest v2.3 or v2.2\"", 110 | "hasInstallScript": true, 111 | "optional": true, 112 | "os": [ 113 | "darwin" 114 | ], 115 | "engines": { 116 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 117 | } 118 | }, 119 | "node_modules/glob": { 120 | "version": "7.2.0", 121 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", 122 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", 123 | "dev": true, 124 | "dependencies": { 125 | "fs.realpath": "^1.0.0", 126 | "inflight": "^1.0.4", 127 | "inherits": "2", 128 | "minimatch": "^3.0.4", 129 | "once": "^1.3.0", 130 | "path-is-absolute": "^1.0.0" 131 | }, 132 | "engines": { 133 | "node": "*" 134 | }, 135 | "funding": { 136 | "url": "https://github.com/sponsors/isaacs" 137 | } 138 | }, 139 | "node_modules/inflight": { 140 | "version": "1.0.6", 141 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 142 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 143 | "dev": true, 144 | "dependencies": { 145 | "once": "^1.3.0", 146 | "wrappy": "1" 147 | } 148 | }, 149 | "node_modules/inherits": { 150 | "version": "2.0.4", 151 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 152 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 153 | "dev": true 154 | }, 155 | "node_modules/minimatch": { 156 | "version": "3.1.2", 157 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 158 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 159 | "dev": true, 160 | "dependencies": { 161 | "brace-expansion": "^1.1.7" 162 | }, 163 | "engines": { 164 | "node": "*" 165 | } 166 | }, 167 | "node_modules/mkdirp": { 168 | "version": "1.0.4", 169 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 170 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", 171 | "dev": true, 172 | "bin": { 173 | "mkdirp": "bin/cmd.js" 174 | }, 175 | "engines": { 176 | "node": ">=10" 177 | } 178 | }, 179 | "node_modules/ms": { 180 | "version": "2.0.0", 181 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 182 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 183 | "dev": true 184 | }, 185 | "node_modules/once": { 186 | "version": "1.4.0", 187 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 188 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 189 | "dev": true, 190 | "dependencies": { 191 | "wrappy": "1" 192 | } 193 | }, 194 | "node_modules/path-is-absolute": { 195 | "version": "1.0.1", 196 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 197 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 198 | "dev": true, 199 | "engines": { 200 | "node": ">=0.10.0" 201 | } 202 | }, 203 | "node_modules/resolve-url": { 204 | "version": "0.2.1", 205 | "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", 206 | "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", 207 | "deprecated": "https://github.com/lydell/resolve-url#deprecated", 208 | "dev": true 209 | }, 210 | "node_modules/rollup": { 211 | "version": "2.26.11", 212 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.26.11.tgz", 213 | "integrity": "sha512-xyfxxhsE6hW57xhfL1I+ixH8l2bdoIMaAecdQiWF3N7IgJEMu99JG+daBiSZQjnBpzFxa0/xZm+3pbCdAQehHw==", 214 | "bin": { 215 | "rollup": "dist/bin/rollup" 216 | }, 217 | "engines": { 218 | "node": ">=10.0.0" 219 | }, 220 | "optionalDependencies": { 221 | "fsevents": "~2.1.2" 222 | } 223 | }, 224 | "node_modules/safer-buffer": { 225 | "version": "2.1.2", 226 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 227 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 228 | "dev": true 229 | }, 230 | "node_modules/sax": { 231 | "version": "1.2.4", 232 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 233 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", 234 | "dev": true 235 | }, 236 | "node_modules/semver": { 237 | "version": "6.3.0", 238 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 239 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", 240 | "dev": true, 241 | "bin": { 242 | "semver": "bin/semver.js" 243 | } 244 | }, 245 | "node_modules/source-map": { 246 | "version": "0.7.3", 247 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", 248 | "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", 249 | "dev": true, 250 | "engines": { 251 | "node": ">= 8" 252 | } 253 | }, 254 | "node_modules/source-map-resolve": { 255 | "version": "0.5.3", 256 | "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", 257 | "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", 258 | "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", 259 | "dev": true, 260 | "dependencies": { 261 | "atob": "^2.1.2", 262 | "decode-uri-component": "^0.2.0", 263 | "resolve-url": "^0.2.1", 264 | "source-map-url": "^0.4.0", 265 | "urix": "^0.1.0" 266 | } 267 | }, 268 | "node_modules/source-map-url": { 269 | "version": "0.4.1", 270 | "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", 271 | "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", 272 | "deprecated": "See https://github.com/lydell/source-map-url#deprecated", 273 | "dev": true 274 | }, 275 | "node_modules/stylus": { 276 | "version": "0.54.8", 277 | "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.8.tgz", 278 | "integrity": "sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg==", 279 | "dev": true, 280 | "dependencies": { 281 | "css-parse": "~2.0.0", 282 | "debug": "~3.1.0", 283 | "glob": "^7.1.6", 284 | "mkdirp": "~1.0.4", 285 | "safer-buffer": "^2.1.2", 286 | "sax": "~1.2.4", 287 | "semver": "^6.3.0", 288 | "source-map": "^0.7.3" 289 | }, 290 | "bin": { 291 | "stylus": "bin/stylus" 292 | }, 293 | "engines": { 294 | "node": "*" 295 | } 296 | }, 297 | "node_modules/urix": { 298 | "version": "0.1.0", 299 | "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", 300 | "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", 301 | "deprecated": "Please see https://github.com/lydell/urix#deprecated", 302 | "dev": true 303 | }, 304 | "node_modules/wrappy": { 305 | "version": "1.0.2", 306 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 307 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 308 | "dev": true 309 | } 310 | }, 311 | "dependencies": { 312 | "atob": { 313 | "version": "2.1.2", 314 | "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", 315 | "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", 316 | "dev": true 317 | }, 318 | "balanced-match": { 319 | "version": "1.0.2", 320 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 321 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 322 | "dev": true 323 | }, 324 | "brace-expansion": { 325 | "version": "1.1.11", 326 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 327 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 328 | "dev": true, 329 | "requires": { 330 | "balanced-match": "^1.0.0", 331 | "concat-map": "0.0.1" 332 | } 333 | }, 334 | "concat-map": { 335 | "version": "0.0.1", 336 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 337 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 338 | "dev": true 339 | }, 340 | "css": { 341 | "version": "2.2.4", 342 | "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", 343 | "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", 344 | "dev": true, 345 | "requires": { 346 | "inherits": "^2.0.3", 347 | "source-map": "^0.6.1", 348 | "source-map-resolve": "^0.5.2", 349 | "urix": "^0.1.0" 350 | }, 351 | "dependencies": { 352 | "source-map": { 353 | "version": "0.6.1", 354 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 355 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 356 | "dev": true 357 | } 358 | } 359 | }, 360 | "css-parse": { 361 | "version": "2.0.0", 362 | "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz", 363 | "integrity": "sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=", 364 | "dev": true, 365 | "requires": { 366 | "css": "^2.0.0" 367 | } 368 | }, 369 | "debug": { 370 | "version": "3.1.0", 371 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 372 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 373 | "dev": true, 374 | "requires": { 375 | "ms": "2.0.0" 376 | } 377 | }, 378 | "decode-uri-component": { 379 | "version": "0.2.0", 380 | "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", 381 | "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", 382 | "dev": true 383 | }, 384 | "fs.realpath": { 385 | "version": "1.0.0", 386 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 387 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 388 | "dev": true 389 | }, 390 | "fsevents": { 391 | "version": "2.1.3", 392 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", 393 | "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", 394 | "optional": true 395 | }, 396 | "glob": { 397 | "version": "7.2.0", 398 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", 399 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", 400 | "dev": true, 401 | "requires": { 402 | "fs.realpath": "^1.0.0", 403 | "inflight": "^1.0.4", 404 | "inherits": "2", 405 | "minimatch": "^3.0.4", 406 | "once": "^1.3.0", 407 | "path-is-absolute": "^1.0.0" 408 | } 409 | }, 410 | "inflight": { 411 | "version": "1.0.6", 412 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 413 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 414 | "dev": true, 415 | "requires": { 416 | "once": "^1.3.0", 417 | "wrappy": "1" 418 | } 419 | }, 420 | "inherits": { 421 | "version": "2.0.4", 422 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 423 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 424 | "dev": true 425 | }, 426 | "minimatch": { 427 | "version": "3.1.2", 428 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 429 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 430 | "dev": true, 431 | "requires": { 432 | "brace-expansion": "^1.1.7" 433 | } 434 | }, 435 | "mkdirp": { 436 | "version": "1.0.4", 437 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 438 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", 439 | "dev": true 440 | }, 441 | "ms": { 442 | "version": "2.0.0", 443 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 444 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 445 | "dev": true 446 | }, 447 | "once": { 448 | "version": "1.4.0", 449 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 450 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 451 | "dev": true, 452 | "requires": { 453 | "wrappy": "1" 454 | } 455 | }, 456 | "path-is-absolute": { 457 | "version": "1.0.1", 458 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 459 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 460 | "dev": true 461 | }, 462 | "resolve-url": { 463 | "version": "0.2.1", 464 | "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", 465 | "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", 466 | "dev": true 467 | }, 468 | "rollup": { 469 | "version": "2.26.11", 470 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.26.11.tgz", 471 | "integrity": "sha512-xyfxxhsE6hW57xhfL1I+ixH8l2bdoIMaAecdQiWF3N7IgJEMu99JG+daBiSZQjnBpzFxa0/xZm+3pbCdAQehHw==", 472 | "requires": { 473 | "fsevents": "~2.1.2" 474 | } 475 | }, 476 | "safer-buffer": { 477 | "version": "2.1.2", 478 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 479 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 480 | "dev": true 481 | }, 482 | "sax": { 483 | "version": "1.2.4", 484 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 485 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", 486 | "dev": true 487 | }, 488 | "semver": { 489 | "version": "6.3.0", 490 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 491 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", 492 | "dev": true 493 | }, 494 | "source-map": { 495 | "version": "0.7.3", 496 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", 497 | "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", 498 | "dev": true 499 | }, 500 | "source-map-resolve": { 501 | "version": "0.5.3", 502 | "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", 503 | "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", 504 | "dev": true, 505 | "requires": { 506 | "atob": "^2.1.2", 507 | "decode-uri-component": "^0.2.0", 508 | "resolve-url": "^0.2.1", 509 | "source-map-url": "^0.4.0", 510 | "urix": "^0.1.0" 511 | } 512 | }, 513 | "source-map-url": { 514 | "version": "0.4.1", 515 | "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", 516 | "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", 517 | "dev": true 518 | }, 519 | "stylus": { 520 | "version": "0.54.8", 521 | "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.8.tgz", 522 | "integrity": "sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg==", 523 | "dev": true, 524 | "requires": { 525 | "css-parse": "~2.0.0", 526 | "debug": "~3.1.0", 527 | "glob": "^7.1.6", 528 | "mkdirp": "~1.0.4", 529 | "safer-buffer": "^2.1.2", 530 | "sax": "~1.2.4", 531 | "semver": "^6.3.0", 532 | "source-map": "^0.7.3" 533 | } 534 | }, 535 | "urix": { 536 | "version": "0.1.0", 537 | "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", 538 | "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", 539 | "dev": true 540 | }, 541 | "wrappy": { 542 | "version": "1.0.2", 543 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 544 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 545 | "dev": true 546 | } 547 | } 548 | } 549 | -------------------------------------------------------------------------------- /examples/rollup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wright-example-rollup", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "wright": "node wright.js" 6 | }, 7 | "dependencies": { 8 | "rollup": "2.26.11" 9 | }, 10 | "devDependencies": { 11 | "stylus": "0.54.8" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/rollup/wright.js: -------------------------------------------------------------------------------- 1 | const wright = require('../../lib') 2 | , rollup = require('rollup') 3 | , stylus = require('stylus') 4 | , fs = require('fs') 5 | 6 | wright({ 7 | debug: true, 8 | run: 'window.reload', 9 | js: { 10 | watch: 'js/**/*.js', 11 | compile: roll 12 | }, 13 | css: { 14 | watch: 'css/**/*.styl', 15 | compile: style 16 | } 17 | }) 18 | 19 | function roll() { 20 | 21 | return rollup.rollup({ 22 | input: 'js/app.js' 23 | }) 24 | .then(bundle => bundle.generate({ format: 'iife' })) 25 | .then(x => x.output[0].code) 26 | } 27 | 28 | function style() { 29 | return new Promise((resolve, reject) => { 30 | fs.readFile('css/style.styl', 'utf8', (err, str) => { 31 | if (err) 32 | return reject(err) 33 | 34 | stylus(str).render((err, css) => err ? reject(err) : resolve(css)) 35 | }) 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /examples/simple/README.md: -------------------------------------------------------------------------------- 1 | # Simple example 2 | 3 | To run this example you need to cd to this directory and have wright installed globally or use the npm run scripts. 4 | 5 | To hot reload js you need to have a global function to call when you script changes. Hot reloading js works by injecting the new script to chrome, but since the page won't be refreshed no initialization will take place (which is exactly what we want). Instead there should be a global function available to run the newly injected script. Call it with the (-r --run) parameter like this 6 | ``` 7 | wright -r "redraw()" index.html 8 | ``` 9 | 10 | Now try to change background color in the style.css file or change the text in the javascript files. If you change the html file a full refresh will also be done. 11 | 12 | You can also start wright with the app.js and style.css file directly like this 13 | ``` 14 | wright -r "redraw()" js/app.js css/style.css 15 | ``` 16 | 17 | When hot reloading javascript you need something in JS that can redraw your view from your apps state. This works very well with various Virtual DOM libraries. 18 | 19 | ## Virtual DOM examples 20 | 21 | - [Mithril](https://github.com/porsager/wright/tree/master/examples/mithril) 22 | 23 | ### Coming soon 24 | - React.js 25 | - Cycle.js 26 | -------------------------------------------------------------------------------- /examples/simple/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | height: 100%; 3 | text-align: center; 4 | padding: 50px; 5 | background: blue; 6 | color: white; 7 | font-family: monospace; 8 | font-size: 22px; 9 | } 10 | 11 | #main { 12 | margin: 50px; 13 | } 14 | -------------------------------------------------------------------------------- /examples/simple/images/pure.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/porsager/wright/84ce6c9bc2dccd2e5032efc8c5ffa4e855e54cae/examples/simple/images/pure.jpg -------------------------------------------------------------------------------- /examples/simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple Wright Example 5 | 6 | 7 | 8 |
9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/simple/js/app.js: -------------------------------------------------------------------------------- 1 | let date = null 2 | let filename 3 | 4 | window.reload = function(payload) { 5 | console.log('hej', payload) 6 | filename = payload.path 7 | date = new Date() 8 | } 9 | 10 | setInterval(function() { 11 | if (date) { 12 | const seconds = parseFloat(Math.round((Date.now() - date.getTime()) / 100) / 10).toFixed(1) 13 | window.main.innerText = 'Hot reloaded ' + filename + ' ' + seconds + ' second' + (seconds === 1 ? '' : 's') + ' ago' 14 | } 15 | }, 100) 16 | 17 | window.main.innerText = 'Refreshed at ' + new Date() 18 | -------------------------------------------------------------------------------- /examples/simple/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wright-example-simple", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /examples/simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wright-example-simple", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "wright": "../../bin/wright -d -r reload index.html" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/browser/catcher.js: -------------------------------------------------------------------------------- 1 | import ubre from './ubre' 2 | 3 | window.onerror = function(msg, file, line, col, err) { // eslint-disable-line 4 | err = (!err || typeof err === 'string') ? { message: msg || err } : err 5 | 6 | err.stack = (!err.stack || String(err) === err.stack) 7 | ? ('at ' + (file || 'unknown') + ':' + line + ':' + col) 8 | : err.stack 9 | 10 | ubre.publish('error', { 11 | message: err.message || String(err), 12 | error: err, 13 | stack: err.stack 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /lib/browser/index.js: -------------------------------------------------------------------------------- 1 | const el = document.getElementById('wrightSocket') 2 | el && el.parentNode.removeChild(el) 3 | 4 | import './p.js' 5 | import './ubre.js' 6 | // import './inspect.js' 7 | import './catcher.js' 8 | -------------------------------------------------------------------------------- /lib/browser/inspect.js: -------------------------------------------------------------------------------- 1 | import ubre from './ubre' 2 | import m from 'mithril' 3 | import b from 'bss' 4 | 5 | const model = { 6 | show: false, 7 | get rect() { 8 | const rect = model.over.getBoundingClientRect() 9 | return rect && { 10 | tag: model.over.tagName.toLowerCase(), 11 | top: rect.top, 12 | left: rect.left, 13 | width: rect.width, 14 | height: rect.height, 15 | bottom: rect.bottom 16 | } 17 | } 18 | } 19 | 20 | const locationRegex = /(.*)[ @(](.*):([\d]*):([\d]*)/i 21 | function parseStackLine(string) { 22 | const [match, func, url, line, column] = (' ' + string.trim()).match(locationRegex) || [] 23 | 24 | return match && { 25 | function: func.trim().replace(/^(global code|at) ?/, ''), 26 | url, 27 | line: parseInt(line), 28 | column: parseInt(column) 29 | } 30 | } 31 | 32 | window.addEventListener('click', function(e) { 33 | if (!model.show) 34 | return 35 | 36 | e.preventDefault() 37 | e.stopPropagation() 38 | 39 | let stack 40 | let el = e.target 41 | while (el.parentNode && !stack) { 42 | stack = el.stackTrace 43 | el = el.parentNode 44 | } 45 | if (stack) { 46 | model.show = false 47 | const parsed = stack.split('\n').map(parseStackLine).filter(a => a) 48 | if (parsed.length > 2 && parsed[2].url) { 49 | ubre.publish('goto', parsed[2]) 50 | e.stopPropagation() 51 | e.preventDefault() 52 | m.redraw() 53 | } 54 | } 55 | }, true) 56 | 57 | window.addEventListener('mouseover', e => { 58 | model.over = e.target 59 | model.show && m.redraw() 60 | }) 61 | 62 | window.addEventListener('keydown', e => { 63 | e.key === 'Shift' && (model.shift = true) 64 | e.key === 'Meta' && (model.meta = true) 65 | e.key === 'Control' && (model.control = true) 66 | update() 67 | }, true) 68 | 69 | window.addEventListener('keyup', e => { 70 | e.key === 'Shift' && (model.shift = false) 71 | e.key === 'Meta' && (model.meta = false) 72 | e.key === 'Control' && (model.control = false) 73 | update() 74 | }, true) 75 | 76 | window.addEventListener('blur', e => { 77 | model.show = false 78 | m.redraw() 79 | }) 80 | 81 | function update() { 82 | const show = model.shift && (model.meta || model.control) 83 | if (model.show !== show) { 84 | model.show = show 85 | m.redraw() 86 | } 87 | } 88 | 89 | const div = document.createElement('div') 90 | div.id = 'wright_inspect' 91 | document.documentElement.appendChild(div) 92 | m.mount(div, { view: () => 93 | model.show && model.rect && m('div' + b` 94 | position fixed 95 | z-index 200000 96 | pointer-events none 97 | transition opacity 0.3s 98 | transform-origin ${ model.rect.left + model.rect.width / 2 }px ${ model.rect.top + model.rect.height / 2 }px 99 | l 0 100 | b 0 101 | r 0 102 | t 0 103 | `.$animate('0.3s', { 104 | from: b` 105 | o 0 106 | transform scale(2) 107 | ` 108 | }), { 109 | onbeforeremove: ({ dom }) => new Promise(res => { 110 | dom.style.opacity = 0 111 | setTimeout(res, 300) 112 | }) 113 | }, 114 | m('span' + b` 115 | ff monospace 116 | fs 10 117 | zi 1 118 | p 2 4 119 | bc white 120 | position absolute 121 | white-space nowrap 122 | br 3 123 | bs 0 0 3px rgba(0,0,0,.5) 124 | t ${ model.rect.bottom + 8} 125 | l ${ model.rect.left } 126 | `.$animate('0.3s', { from: b`o 0` }), 127 | Math.round(model.rect.left) + ',' + Math.round(model.rect.top) + ' <' + model.rect.tag + '> ' + Math.round(model.rect.width) + 'x' + Math.round(model.rect.height) 128 | ), 129 | m('svg' + b` 130 | position absolute 131 | top 0 132 | left 0 133 | `, { 134 | width: '100%', 135 | height: '100%' 136 | }, 137 | m('defs', 138 | m('mask#hole', 139 | m('rect', { 140 | width: 10000, 141 | height: 10000, 142 | fill: 'white' 143 | }), 144 | m('rect' + b` 145 | transition all 0.3s 146 | `, { 147 | fill: 'black', 148 | rx: 4, 149 | ry: 4, 150 | width: model.rect.width + 8, 151 | height: model.rect.height + 8, 152 | x: model.rect.left - 4, 153 | y: model.rect.top - 4 154 | }) 155 | ) 156 | ), 157 | m('rect', { 158 | fill: 'rgba(0, 150, 255, 0.5)', 159 | width: '100%', 160 | height: '100%', 161 | mask: 'url(#hole)' 162 | }) 163 | ) 164 | ) 165 | }) 166 | -------------------------------------------------------------------------------- /lib/browser/p.js: -------------------------------------------------------------------------------- 1 | if (!('p' in window)) { 2 | window.p = function print(x) { 3 | if (Array.isArray(x) && Array.isArray(x.raw)) 4 | return (...rest) => (window.p(x[0], ...rest), rest[0]) 5 | 6 | window.console.log.apply(console, arguments) 7 | return x 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/browser/ubre.js: -------------------------------------------------------------------------------- 1 | import Pws from 'pws' 2 | import Ubre from 'ubre' 3 | 4 | const protocol = location.protocol === 'https:' ? 'wss' : 'ws' 5 | , socket = new Pws(protocol + '://' + location.host + '/wright') 6 | , ubre = Ubre.ws(socket) 7 | 8 | ubre.subscribe('reload', () => !window.wright && location.reload()) 9 | ubre.subscribe('run', ({ method, arg }) => { 10 | const fn = method.split('.').reduce((acc, m) => acc[m] || {}, window) 11 | typeof fn === 'function' 12 | ? fn(arg) 13 | : ubre.publish('error', { message: 'Couldn\'t find window.' + method }) 14 | }) 15 | 16 | let opened = false 17 | 18 | socket.addEventListener('open', () => { 19 | opened && location.reload() 20 | opened = true 21 | }) 22 | 23 | export default ubre 24 | -------------------------------------------------------------------------------- /lib/browser/wright.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | if (!('p' in window)) { 5 | window.p = function print(x) { 6 | if (Array.isArray(x) && Array.isArray(x.raw)) 7 | { return function () { 8 | var rest = [], len = arguments.length; 9 | while ( len-- ) rest[ len ] = arguments[ len ]; 10 | 11 | return (window.p.apply(window, [ x[0] ].concat( rest )), rest[0]); 12 | } } 13 | 14 | window.console.log.apply(console, arguments); 15 | return x 16 | }; 17 | } 18 | 19 | function index(url, protocols, WebSocket, options) { 20 | if (typeof protocols === 'function') { 21 | if (typeof WebSocket === 'object') 22 | { options = WebSocket; } 23 | WebSocket = protocols; 24 | protocols = undefined; 25 | } 26 | 27 | if (!Array.isArray(protocols) && typeof protocols === 'object') { 28 | options = protocols; 29 | protocols = undefined; 30 | } 31 | 32 | if (typeof WebSocket === 'object') { 33 | options = WebSocket; 34 | WebSocket = undefined; 35 | } 36 | 37 | var browser = typeof window !== 'undefined' && window.WebSocket; 38 | if (browser) { 39 | WebSocket = WebSocket || window.WebSocket; 40 | typeof window !== 'undefined' 41 | && typeof window.addEventListener === 'function' 42 | && window.addEventListener('online', function () { return connect(); }); 43 | } 44 | 45 | if (!WebSocket) 46 | { throw new Error('Please supply a websocket library to use') } 47 | 48 | if (!options) 49 | { options = {}; } 50 | 51 | var connection = null 52 | , reconnecting = false 53 | , reconnectTimer = null 54 | , heartbeatTimer = null 55 | , openTimer = null 56 | , binaryType = null 57 | , closed = false 58 | , reconnectDelay = 0; 59 | 60 | var listeners = {}; 61 | var listenerHandlers = {}; 62 | var ons = {}; 63 | var onHandlers = {}; 64 | 65 | var pws = { 66 | CONNECTING: 0, 67 | OPEN : 1, 68 | CLOSING : 2, 69 | CLOSED : 3, 70 | get readyState() { return connection.readyState }, 71 | get protocol() { return connection.protocol }, 72 | get extensions() { return connection.extensions }, 73 | get bufferedAmount() { return connection.bufferedAmount }, 74 | get binaryType() { return connection.binaryType }, 75 | set binaryType(type) { 76 | binaryType = type; 77 | connection.binaryType = type; 78 | }, 79 | connect: connect, 80 | url: url, 81 | retries: 0, 82 | pingTimeout: 'pingTimeout' in options ? options.pingTimeout : false, 83 | maxTimeout: options.maxTimeout || 5 * 60 * 1000, 84 | maxRetries: options.maxRetries || 0, 85 | nextReconnectDelay: options.nextReconnectDelay || function reconnectTimeout(retries) { 86 | return Math.min((1 + Math.random()) * Math.pow(1.5, retries) * 1000, pws.maxTimeout) 87 | }, 88 | send: function() { 89 | connection.send.apply(connection, arguments); 90 | }, 91 | close: function() { 92 | clearTimeout(reconnectTimer); 93 | closed = true; 94 | connection && connection.close.apply(connection, arguments); 95 | }, 96 | onopen: options.onopen, 97 | onmessage: options.onmessage, 98 | onclose: options.onclose, 99 | onerror: options.onerror 100 | }; 101 | 102 | var on = function (method, events, handlers) { return function (event, fn, options) { 103 | function handler(e) { 104 | options && options.once && connection[method === 'on' ? 'off' : 'removeEventListener'](event, handler); 105 | e && typeof e === 'object' && reconnectDelay && (e.reconnectDelay = reconnectDelay); 106 | fn.apply(pws, arguments); 107 | } 108 | 109 | event in events ? events[event].push(fn) : (events[event] = [fn]); 110 | event in handlers ? handlers[event].push(handler) : (handlers[event] = [handler]); 111 | connection && connection[method](event, handler); 112 | }; }; 113 | 114 | var off = function (method, events, handlers) { return function (event, fn) { 115 | var index = events[event].indexOf(fn); 116 | if (index === -1) 117 | { return } 118 | 119 | connection && connection[method](event, handlers[event][index]); 120 | 121 | events[event].splice(index, 1); 122 | handlers[event].splice(index, 1); 123 | }; }; 124 | 125 | pws.addEventListener = on('addEventListener', listeners, listenerHandlers); 126 | pws.removeEventListener = off('removeEventListener', listeners, listenerHandlers); 127 | pws.on = on('on', ons, onHandlers); 128 | pws.off = off('off', ons, onHandlers); 129 | pws.once = function (event, fn) { return pws.on(event, fn, { once: true }); }; 130 | 131 | if (url) 132 | { connect(); } 133 | 134 | return pws 135 | 136 | function connect(url) { 137 | closed = reconnecting = false; 138 | clearTimeout(reconnectTimer); 139 | 140 | if (connection && connection.readyState !== 3) { 141 | close(4665, 'Manual connect initiated'); 142 | return connect(url) 143 | } 144 | 145 | url && (pws.url = url); 146 | url = typeof pws.url === 'function' 147 | ? pws.url(pws) 148 | : pws.url; 149 | 150 | connection = browser 151 | ? protocols 152 | ? new WebSocket(url, protocols) 153 | : new WebSocket(url) 154 | : new WebSocket(url, protocols, options); 155 | 156 | typeof connection.on === 'function' 157 | ? connection.on('error', onerror) 158 | : (connection.onerror = onerror); 159 | 160 | connection.onclose = onclose; 161 | connection.onopen = onopen; 162 | connection.onmessage = onmessage; 163 | Object.keys(listenerHandlers).forEach(function (event) { 164 | listenerHandlers[event].forEach(function (handler) { return connection.addEventListener(event, handler); }); 165 | }); 166 | Object.keys(onHandlers).forEach(function (event) { 167 | onHandlers[event].forEach(function (handler) { return connection.on(event, handler); }); 168 | }); 169 | 170 | if (binaryType) 171 | { connection.binaryType = binaryType; } 172 | } 173 | 174 | function onclose(event) { 175 | event.reconnectDelay = reconnect(); 176 | pws.onclose && pws.onclose.apply(pws, arguments); 177 | clearTimeout(heartbeatTimer); 178 | clearTimeout(openTimer); 179 | } 180 | 181 | function onerror() { 182 | pws.onerror && pws.onerror.apply(pws, arguments); 183 | } 184 | 185 | function onopen(event) { 186 | pws.onopen && pws.onopen.apply(pws, arguments); 187 | heartbeat(); 188 | openTimer = setTimeout(function () { return pws.retries = 0; }, reconnectDelay || 0); 189 | } 190 | 191 | function onmessage(event) { 192 | pws.onmessage && pws.onmessage.apply(pws, arguments); 193 | heartbeat(); 194 | } 195 | 196 | function heartbeat() { 197 | if (!pws.pingTimeout) 198 | { return } 199 | 200 | clearTimeout(heartbeatTimer); 201 | heartbeatTimer = setTimeout(timedOut, pws.pingTimeout); 202 | } 203 | 204 | function timedOut() { 205 | close(4663, 'No heartbeat received within ' + pws.pingTimeout + 'ms'); 206 | } 207 | 208 | function reconnect() { 209 | if (closed) 210 | { return } 211 | 212 | if (reconnecting) 213 | { return reconnectDelay - (Date.now() - reconnecting) } 214 | 215 | if (pws.maxRetries && pws.connects >= pws.maxRetries) 216 | { return } 217 | 218 | reconnecting = Date.now(); 219 | reconnectDelay = Math.ceil(pws.nextReconnectDelay(pws.retries++)); 220 | reconnectTimer = setTimeout(connect, reconnectDelay); 221 | 222 | return reconnectDelay 223 | } 224 | 225 | function close(code, reason) { 226 | connection.onclose = connection.onopen = connection.onerror = connection.onmessage = null; 227 | Object.keys(listenerHandlers).forEach(function (event) { 228 | listenerHandlers[event].forEach(function (handler) { return connection.removeEventListener(event, handler); }); 229 | }); 230 | Object.keys(onHandlers).forEach(function (event) { 231 | onHandlers[event].forEach(function (handler) { return connection.removeListener(event, handler); }); 232 | }); 233 | connection.close(); 234 | connection = null; 235 | var event = closeEvent(code, reason); 236 | onclose(event); 237 | listenerHandlers.close && listenerHandlers.close.forEach(function (handler) { return handler(event); }); 238 | onHandlers.close && onHandlers.close.forEach(function (handler) { return handler(code, reason, reconnectDelay); }); 239 | } 240 | 241 | function closeEvent(code, reason) { 242 | var event; 243 | 244 | if (typeof window !== 'undefined' && window.CloseEvent) { 245 | event = new window.CloseEvent('HeartbeatTimeout', { wasClean: true, code: code, reason: reason }); 246 | } else { 247 | event = new Error('HeartbeatTimeout'); 248 | event.code = code; 249 | event.reason = reason; 250 | } 251 | 252 | return event 253 | } 254 | } 255 | 256 | var noop = function () { /* noop */ }; 257 | 258 | function Ubre(ref) { 259 | var send = ref.send; if ( send === void 0 ) send = noop; 260 | var receive = ref.receive; if ( receive === void 0 ) receive = noop; 261 | var open = ref.open; if ( open === void 0 ) open = false; 262 | var deserialize = ref.deserialize; if ( deserialize === void 0 ) deserialize = JSON.parse; 263 | var serialize = ref.serialize; if ( serialize === void 0 ) serialize = JSON.stringify; 264 | var unwrapError = ref.unwrapError; if ( unwrapError === void 0 ) unwrapError = unwrapErr; 265 | 266 | var subscriptions = MapSet() 267 | , subscribers = MapSet() 268 | , responses = MapSet() 269 | , queue = MapSet() 270 | , requests = Map() 271 | , publishes = Map() 272 | , handlers = Map(); 273 | 274 | var i = 0; 275 | 276 | function subscribe(from, ref) { 277 | var subscribe = ref.subscribe; 278 | 279 | !subscribers.has(subscribe) && ubre.onTopicStart(subscribe); 280 | subscribers.add(subscribe, from); 281 | ubre.onSubscribe(subscribe, from); 282 | } 283 | 284 | function unsubscribe(from, ref) { 285 | var unsubscribe = ref.unsubscribe; 286 | 287 | subscribers.remove(unsubscribe, from); 288 | !subscribers.has(unsubscribe) && ubre.onTopicEnd(unsubscribe); 289 | ubre.onUnsubscribe(unsubscribe, from); 290 | } 291 | 292 | function publish(from, ref) { 293 | var publish = ref.publish; 294 | var body = ref.body; 295 | 296 | subscriptions.has(publish) && subscriptions.get(publish).forEach(function (s) { return ( 297 | (!s.target || s.target === from) && s.fn(body, from) 298 | ); }); 299 | } 300 | 301 | function request(from, ref) { 302 | var id = ref.id; 303 | var request = ref.request; 304 | var body = ref.body; 305 | 306 | if (!handlers.has(request)) 307 | { return forward({ fail: id, body: 'NotFound' }, from) } 308 | 309 | responses.add(from, { id: id }); 310 | new Promise(function (resolve) { return resolve(handlers.get(request)(body, from)); }) 311 | .then(function (body) { return sendResponse(from, id, { success: id, body: body }); }) 312 | .catch(function (body) { return sendResponse(from, id, { fail: id, body: unwrapError(body) }); }); 313 | } 314 | 315 | function success(from, ref) { 316 | var success = ref.success; 317 | var body = ref.body; 318 | 319 | requests.has(success) && requests.get(success).resolve(body); 320 | requests.delete(success); 321 | } 322 | 323 | function fail(from, ref) { 324 | var fail = ref.fail; 325 | var body = ref.body; 326 | 327 | requests.has(fail) && requests.get(fail).reject(body); 328 | requests.delete(fail); 329 | } 330 | 331 | function sendResponse(target, id, message) { 332 | if (open) { 333 | forward(message, target), 334 | responses.remove(target, id); 335 | } else { 336 | queue.add(target, { id: id, message: message }); 337 | } 338 | } 339 | 340 | function forward(message, target) { 341 | send(serialize(message), target); 342 | } 343 | 344 | function ubre(target) { 345 | return Object.create(ubre, { 346 | target: { 347 | writable: false, 348 | configurable: false, 349 | value: target 350 | } 351 | }) 352 | } 353 | 354 | ubre.onTopicStart = noop; 355 | ubre.onTopicEnd = noop; 356 | ubre.onSubscribe = noop; 357 | ubre.onUnsubscribe = noop; 358 | 359 | ubre.message = function (message, from) { 360 | var x = deserialize(message); 361 | 'subscribe' in x ? subscribe(from, x) : 362 | 'unsubscribe' in x ? unsubscribe(from, x) : 363 | 'publish' in x ? publish(from, x) : 364 | 'request' in x && 'id' in x ? request(from, x) : 365 | 'success' in x ? success(from, x) : 366 | 'fail' in x && fail(from, x); 367 | }; 368 | 369 | ubre.publish = function(topic, body) { 370 | subscribers.has(topic) && (this.target 371 | ? subscribers.get(topic).has(this.target) && forward({ publish: topic, body: body }, this.target) 372 | : subscribers.get(topic).forEach(function (s) { return open 373 | ? forward({ publish: topic, body: body }, s) 374 | : publishes.set({ publish: topic, body: body }, s); } 375 | ) 376 | ); 377 | }; 378 | 379 | ubre.subscribe = function(topic, body, fn) { 380 | var this$1 = this; 381 | 382 | if (arguments.length === 2) { 383 | fn = body; 384 | body = undefined; 385 | } 386 | 387 | open && forward({ subscribe: topic, body: body }, this.target); 388 | var subscription = { body: body, fn: fn, sent: open, target: this.target }; 389 | subscriptions.add(topic, subscription); 390 | 391 | return { 392 | unsubscribe: function () { 393 | open && forward({ unsubscribe: topic, body: body }, this$1.target); 394 | subscriptions.remove(topic, subscription); 395 | } 396 | } 397 | }; 398 | 399 | ubre.request = function(request, body, id) { 400 | var this$1 = this; 401 | 402 | id = id || ++i; 403 | return new Promise(function (resolve, reject) { 404 | try { 405 | open && forward({ request: request, id: id, body: body }, this$1.target); 406 | requests.set(id, { resolve: resolve, reject: reject, request: request, body: body, sent: open, target: this$1.target }); 407 | } catch (err) { 408 | reject(err); 409 | } 410 | }) 411 | }; 412 | 413 | ubre.handle = function (request, fn) { 414 | typeof request === 'object' 415 | ? Object.keys(request).forEach(function (h) { return ubre.handle(h, request[h]); }) 416 | : handlers.set(request, fn); 417 | }; 418 | 419 | ubre.open = function () { 420 | open = true; 421 | 422 | subscriptions.forEach(function (s, topic) { return s.forEach(function (m) { return !m.sent && ( 423 | forward({ subscribe: topic, body: m.body }, m.target), 424 | m.sent = true 425 | ); }); }); 426 | 427 | requests.forEach(function (r, id) { return !r.sent && ( 428 | forward({ request: r.request, id: id, body: r.body }, r.target), 429 | r.sent = true 430 | ); }); 431 | 432 | queue.forEach(function (ref, from) { 433 | var id = ref.id; 434 | var message = ref.message; 435 | 436 | return sendResponse(from, id, message); 437 | } 438 | ); 439 | 440 | publishes.forEach(function (target, p) { 441 | forward(p, target); 442 | publishes.delete(p); 443 | }); 444 | }; 445 | 446 | ubre.close = function() { 447 | var this$1 = this; 448 | 449 | if (this.target) { 450 | subscribers.removeItems(this.target); 451 | subscriptions.forEach(function (s) { return s.forEach(function (ref) { 452 | var target = ref.target; 453 | 454 | return target === this$1.target && s.delete(target); 455 | } 456 | ); }); 457 | requests.forEach(function (r, id) { return r.target === this$1.target && ( 458 | r.reject(new Error('closed')), 459 | requests.delete(id) 460 | ); }); 461 | responses.delete(this.target); 462 | queue.delete(this.target); 463 | } else { 464 | open = false; 465 | subscriptions.forEach(function (s) { return s.forEach(function (m) { return m.sent = false; }); }); 466 | } 467 | }; 468 | 469 | receive(ubre.message); 470 | 471 | return ubre 472 | } 473 | 474 | function MapSet() { 475 | var map = Map(); 476 | 477 | return { 478 | add: function (key, item) { return (map.get(key) || map.set(key, Set()).get(key)).add(item); }, 479 | has: map.has.bind(map), 480 | get: map.get.bind(map), 481 | delete: map.delete.bind(map), 482 | clear: map.clear.bind(map), 483 | forEach: map.forEach.bind(map), 484 | removeItems: function (item) { return map.forEach(function (set) { return set.delete(item); }); }, 485 | remove: function (key, item) { 486 | if (!map.has(key)) 487 | { return } 488 | 489 | var set = map.get(key); 490 | set.delete(item); 491 | set.size === 0 && map.delete(key); 492 | } 493 | } 494 | } 495 | 496 | function Map() { 497 | var keys = [] 498 | , values = []; 499 | 500 | var map = { 501 | has: function (x) { return keys.indexOf(x) !== -1; }, 502 | get: function (x) { return values[keys.indexOf(x)]; }, 503 | set: function (x, v) { return (keys.push(x), values.push(v), map); }, 504 | forEach: function (fn) { return keys.forEach(function (k, i) { return fn(values[i], k, map); }); }, 505 | clear: function () { return (keys = [], values = [], undefined); }, 506 | delete: function (x) { 507 | var index = keys.indexOf(x); 508 | if (index > -1) { 509 | keys.splice(index, 1); 510 | values.splice(index, 1); 511 | } 512 | } 513 | }; 514 | 515 | return map 516 | } 517 | 518 | function Set() { 519 | var values = []; 520 | 521 | var set = { 522 | add: function (x) { return (values.indexOf(x) === -1 && values.push(x), set); }, 523 | clear: function () { return (values = [], undefined); }, 524 | delete: function (x) { return values.indexOf(x) !== -1 ? (values.splice(values.indexOf(x), 1), true) : false; }, 525 | forEach: function (fn) { return values.forEach(function (v) { return fn(v, v, set); }); }, 526 | has: function (x) { return values.indexOf(x) !== -1; } 527 | }; 528 | 529 | Object.defineProperty(set, 'size', { 530 | get: function get() { 531 | return values.length 532 | } 533 | }); 534 | 535 | return set 536 | } 537 | 538 | function copy(o, seen) { 539 | if ( seen === void 0 ) seen = []; 540 | 541 | return Object.keys(o).reduce(function (acc, key) { return ( 542 | acc[key] = o[key] && typeof o[key] === 'object' 543 | ? ( 544 | seen.push(o), 545 | seen.indexOf(o[key]) > -1 546 | ? '[Circular]' 547 | : copy(o[key], seen)) 548 | : o[key], 549 | acc 550 | ); }, Array.isArray(o) ? [] : {}) 551 | } 552 | 553 | var common = ['name', 'message', 'stack', 'code']; 554 | function unwrapErr(error) { 555 | if (typeof error === 'function') 556 | { return '[Function: ' + (error.name || 'anonymous') + ']' } 557 | 558 | if (typeof error !== 'object') 559 | { return error } 560 | 561 | var err = copy(error); 562 | common.forEach(function (c) { return typeof error[c] === 'string' && (err[c] = error[c]); } 563 | ); 564 | return err 565 | } 566 | 567 | function client(ws, options) { 568 | options = options || {}; 569 | if (typeof options.send !== 'function') 570 | { options.send = function (message) { return ws.send(message); }; } 571 | 572 | var ubre = Ubre(options); 573 | 574 | ws.addEventListener('message', function (e) { return e.data && e.data[0] === '{' && ubre.message(e.data, ws); }); 575 | ws.addEventListener('open', function () { return ubre.open(); }); 576 | ws.addEventListener('close', function () { return ubre.close(); }); 577 | 578 | ubre.ws = ws; 579 | 580 | return ubre 581 | } 582 | 583 | function server(server, options) { 584 | options = options || {}; 585 | if (typeof options.send !== 'function') 586 | { options.send = function (message, ws) { return ws.send(message); }; } 587 | 588 | if (!('open' in options)) 589 | { options.open = true; } 590 | 591 | var ubre = Ubre(options); 592 | ubre.wss = server; 593 | server.on('connection', function (ws) { 594 | ws.on('message', function (data) { return data[0] === '{' && ubre.message(data, ws); }); 595 | ws.on('close', function () { return ubre(ws).close(); }); 596 | }); 597 | 598 | return ubre 599 | } 600 | 601 | Ubre.ws = client; 602 | Ubre.wss = server; 603 | 604 | var protocol = location.protocol === 'https:' ? 'wss' : 'ws' 605 | , socket = new index(protocol + '://' + location.host + '/wright') 606 | , ubre = Ubre.ws(socket); 607 | 608 | ubre.subscribe('reload', function () { return !window.wright && location.reload(); }); 609 | ubre.subscribe('run', function (ref) { 610 | var method = ref.method; 611 | var arg = ref.arg; 612 | 613 | var fn = method.split('.').reduce(function (acc, m) { return acc[m] || {}; }, window); 614 | typeof fn === 'function' 615 | ? fn(arg) 616 | : ubre.publish('error', { message: 'Couldn\'t find window.' + method }); 617 | }); 618 | 619 | var opened = false; 620 | 621 | socket.addEventListener('open', function () { 622 | opened && location.reload(); 623 | opened = true; 624 | }); 625 | 626 | window.onerror = function(msg, file, line, col, err) { // eslint-disable-line 627 | err = (!err || typeof err === 'string') ? { message: msg || err } : err; 628 | 629 | err.stack = (!err.stack || String(err) === err.stack) 630 | ? ('at ' + (file || 'unknown') + ':' + line + ':' + col) 631 | : err.stack; 632 | 633 | ubre.publish('error', { 634 | message: err.message || String(err), 635 | error: err, 636 | stack: err.stack 637 | }); 638 | }; 639 | 640 | var el = document.getElementById('wrightSocket'); 641 | el && el.parentNode.removeChild(el); 642 | 643 | }()); 644 | //# sourceMappingURL=wright.js.map 645 | -------------------------------------------------------------------------------- /lib/browser/wright.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"wright.js","sources":["p.js","../../node_modules/pws/dist/index.esm.js","../../node_modules/ubre/src/ubre.js","../../node_modules/ubre/src/websocket.js","../../node_modules/ubre/src/index.js","ubre.js","catcher.js","index.js"],"sourcesContent":["if (!('p' in window)) {\n window.p = function print(x) {\n if (Array.isArray(x) && Array.isArray(x.raw))\n return (...rest) => (window.p(x[0], ...rest), rest[0])\n\n window.console.log.apply(console, arguments)\n return x\n }\n}\n","function index(url, protocols, WebSocket, options) {\n if (typeof protocols === 'function') {\n if (typeof WebSocket === 'object')\n { options = WebSocket; }\n WebSocket = protocols;\n protocols = undefined;\n }\n\n if (!Array.isArray(protocols) && typeof protocols === 'object') {\n options = protocols;\n protocols = undefined;\n }\n\n if (typeof WebSocket === 'object') {\n options = WebSocket;\n WebSocket = undefined;\n }\n\n var browser = typeof window !== 'undefined' && window.WebSocket;\n if (browser) {\n WebSocket = WebSocket || window.WebSocket;\n typeof window !== 'undefined'\n && typeof window.addEventListener === 'function'\n && window.addEventListener('online', function () { return connect(); });\n }\n\n if (!WebSocket)\n { throw new Error('Please supply a websocket library to use') }\n\n if (!options)\n { options = {}; }\n\n var connection = null\n , reconnecting = false\n , reconnectTimer = null\n , heartbeatTimer = null\n , openTimer = null\n , binaryType = null\n , closed = false\n , reconnectDelay = 0;\n\n var listeners = {};\n var listenerHandlers = {};\n var ons = {};\n var onHandlers = {};\n\n var pws = {\n CONNECTING: 0,\n OPEN : 1,\n CLOSING : 2,\n CLOSED : 3,\n get readyState() { return connection.readyState },\n get protocol() { return connection.protocol },\n get extensions() { return connection.extensions },\n get bufferedAmount() { return connection.bufferedAmount },\n get binaryType() { return connection.binaryType },\n set binaryType(type) {\n binaryType = type;\n connection.binaryType = type;\n },\n connect: connect,\n url: url,\n retries: 0,\n pingTimeout: 'pingTimeout' in options ? options.pingTimeout : false,\n maxTimeout: options.maxTimeout || 5 * 60 * 1000,\n maxRetries: options.maxRetries || 0,\n nextReconnectDelay: options.nextReconnectDelay || function reconnectTimeout(retries) {\n return Math.min((1 + Math.random()) * Math.pow(1.5, retries) * 1000, pws.maxTimeout)\n },\n send: function() {\n connection.send.apply(connection, arguments);\n },\n close: function() {\n clearTimeout(reconnectTimer);\n closed = true;\n connection && connection.close.apply(connection, arguments);\n },\n onopen: options.onopen,\n onmessage: options.onmessage,\n onclose: options.onclose,\n onerror: options.onerror\n };\n\n var on = function (method, events, handlers) { return function (event, fn, options) {\n function handler(e) {\n options && options.once && connection[method === 'on' ? 'off' : 'removeEventListener'](event, handler);\n e && typeof e === 'object' && reconnectDelay && (e.reconnectDelay = reconnectDelay);\n fn.apply(pws, arguments);\n }\n\n event in events ? events[event].push(fn) : (events[event] = [fn]);\n event in handlers ? handlers[event].push(handler) : (handlers[event] = [handler]);\n connection && connection[method](event, handler);\n }; };\n\n var off = function (method, events, handlers) { return function (event, fn) {\n var index = events[event].indexOf(fn);\n if (index === -1)\n { return }\n\n connection && connection[method](event, handlers[event][index]);\n\n events[event].splice(index, 1);\n handlers[event].splice(index, 1);\n }; };\n\n pws.addEventListener = on('addEventListener', listeners, listenerHandlers);\n pws.removeEventListener = off('removeEventListener', listeners, listenerHandlers);\n pws.on = on('on', ons, onHandlers);\n pws.off = off('off', ons, onHandlers);\n pws.once = function (event, fn) { return pws.on(event, fn, { once: true }); };\n\n if (url)\n { connect(); }\n\n return pws\n\n function connect(url) {\n closed = reconnecting = false;\n clearTimeout(reconnectTimer);\n\n if (connection && connection.readyState !== 3) {\n close(4665, 'Manual connect initiated');\n return connect(url)\n }\n\n url && (pws.url = url);\n url = typeof pws.url === 'function'\n ? pws.url(pws)\n : pws.url;\n\n connection = browser\n ? protocols\n ? new WebSocket(url, protocols)\n : new WebSocket(url)\n : new WebSocket(url, protocols, options);\n\n typeof connection.on === 'function'\n ? connection.on('error', onerror)\n : (connection.onerror = onerror);\n\n connection.onclose = onclose;\n connection.onopen = onopen;\n connection.onmessage = onmessage;\n Object.keys(listenerHandlers).forEach(function (event) {\n listenerHandlers[event].forEach(function (handler) { return connection.addEventListener(event, handler); });\n });\n Object.keys(onHandlers).forEach(function (event) {\n onHandlers[event].forEach(function (handler) { return connection.on(event, handler); });\n });\n\n if (binaryType)\n { connection.binaryType = binaryType; }\n }\n\n function onclose(event) {\n event.reconnectDelay = reconnect();\n pws.onclose && pws.onclose.apply(pws, arguments);\n clearTimeout(heartbeatTimer);\n clearTimeout(openTimer);\n }\n\n function onerror() {\n pws.onerror && pws.onerror.apply(pws, arguments);\n }\n\n function onopen(event) {\n pws.onopen && pws.onopen.apply(pws, arguments);\n heartbeat();\n openTimer = setTimeout(function () { return pws.retries = 0; }, reconnectDelay || 0);\n }\n\n function onmessage(event) {\n pws.onmessage && pws.onmessage.apply(pws, arguments);\n heartbeat();\n }\n\n function heartbeat() {\n if (!pws.pingTimeout)\n { return }\n\n clearTimeout(heartbeatTimer);\n heartbeatTimer = setTimeout(timedOut, pws.pingTimeout);\n }\n\n function timedOut() {\n close(4663, 'No heartbeat received within ' + pws.pingTimeout + 'ms');\n }\n\n function reconnect() {\n if (closed)\n { return }\n\n if (reconnecting)\n { return reconnectDelay - (Date.now() - reconnecting) }\n\n if (pws.maxRetries && pws.connects >= pws.maxRetries)\n { return }\n\n reconnecting = Date.now();\n reconnectDelay = Math.ceil(pws.nextReconnectDelay(pws.retries++));\n reconnectTimer = setTimeout(connect, reconnectDelay);\n\n return reconnectDelay\n }\n\n function close(code, reason) {\n connection.onclose = connection.onopen = connection.onerror = connection.onmessage = null;\n Object.keys(listenerHandlers).forEach(function (event) {\n listenerHandlers[event].forEach(function (handler) { return connection.removeEventListener(event, handler); });\n });\n Object.keys(onHandlers).forEach(function (event) {\n onHandlers[event].forEach(function (handler) { return connection.removeListener(event, handler); });\n });\n connection.close();\n connection = null;\n var event = closeEvent(code, reason);\n onclose(event);\n listenerHandlers.close && listenerHandlers.close.forEach(function (handler) { return handler(event); });\n onHandlers.close && onHandlers.close.forEach(function (handler) { return handler(code, reason, reconnectDelay); });\n }\n\n function closeEvent(code, reason) {\n var event;\n\n if (typeof window !== 'undefined' && window.CloseEvent) {\n event = new window.CloseEvent('HeartbeatTimeout', { wasClean: true, code: code, reason: reason });\n } else {\n event = new Error('HeartbeatTimeout');\n event.code = code;\n event.reason = reason;\n }\n\n return event\n }\n}\n\nexport default index;\n","export default Ubre\n\nconst noop = () => { /* noop */ }\n\nfunction Ubre({\n send = noop,\n receive = noop,\n open = false,\n deserialize = JSON.parse,\n serialize = JSON.stringify,\n unwrapError = unwrapErr\n}) {\n const subscriptions = MapSet()\n , subscribers = MapSet()\n , responses = MapSet()\n , queue = MapSet()\n , requests = Map()\n , publishes = Map()\n , handlers = Map()\n\n let i = 0\n\n function subscribe(from, { subscribe }) {\n !subscribers.has(subscribe) && ubre.onTopicStart(subscribe)\n subscribers.add(subscribe, from)\n ubre.onSubscribe(subscribe, from)\n }\n\n function unsubscribe(from, { unsubscribe }) {\n subscribers.remove(unsubscribe, from)\n !subscribers.has(unsubscribe) && ubre.onTopicEnd(unsubscribe)\n ubre.onUnsubscribe(unsubscribe, from)\n }\n\n function publish(from, { publish, body }) {\n subscriptions.has(publish) && subscriptions.get(publish).forEach(s => (\n (!s.target || s.target === from) && s.fn(body, from)\n ))\n }\n\n function request(from, { id, request, body }) {\n if (!handlers.has(request))\n return forward({ fail: id, body: 'NotFound' }, from)\n\n responses.add(from, { id })\n new Promise(resolve => resolve(handlers.get(request)(body, from)))\n .then(body => sendResponse(from, id, { success: id, body }))\n .catch(body => sendResponse(from, id, { fail: id, body: unwrapError(body) }))\n }\n\n function success(from, { success, body }) {\n requests.has(success) && requests.get(success).resolve(body)\n requests.delete(success)\n }\n\n function fail(from, { fail, body }) {\n requests.has(fail) && requests.get(fail).reject(body)\n requests.delete(fail)\n }\n\n function sendResponse(target, id, message) {\n if (open) {\n forward(message, target),\n responses.remove(target, id)\n } else {\n queue.add(target, { id, message })\n }\n }\n\n function forward(message, target) {\n send(serialize(message), target)\n }\n\n function ubre(target) {\n return Object.create(ubre, {\n target: {\n writable: false,\n configurable: false,\n value: target\n }\n })\n }\n\n ubre.onTopicStart = noop\n ubre.onTopicEnd = noop\n ubre.onSubscribe = noop\n ubre.onUnsubscribe = noop\n\n ubre.message = (message, from) => {\n const x = deserialize(message)\n 'subscribe' in x ? subscribe(from, x) :\n 'unsubscribe' in x ? unsubscribe(from, x) :\n 'publish' in x ? publish(from, x) :\n 'request' in x && 'id' in x ? request(from, x) :\n 'success' in x ? success(from, x) :\n 'fail' in x && fail(from, x)\n }\n\n ubre.publish = function(topic, body) {\n subscribers.has(topic) && (this.target\n ? subscribers.get(topic).has(this.target) && forward({ publish: topic, body }, this.target)\n : subscribers.get(topic).forEach(s => open\n ? forward({ publish: topic, body }, s)\n : publishes.set({ publish: topic, body }, s)\n )\n )\n }\n\n ubre.subscribe = function(topic, body, fn) {\n if (arguments.length === 2) {\n fn = body\n body = undefined\n }\n\n open && forward({ subscribe: topic, body }, this.target)\n const subscription = { body, fn, sent: open, target: this.target }\n subscriptions.add(topic, subscription)\n\n return {\n unsubscribe: () => {\n open && forward({ unsubscribe: topic, body }, this.target)\n subscriptions.remove(topic, subscription)\n }\n }\n }\n\n ubre.request = function(request, body, id) {\n id = id || ++i\n return new Promise((resolve, reject) => {\n try {\n open && forward({ request, id, body }, this.target)\n requests.set(id, { resolve, reject, request, body, sent: open, target: this.target })\n } catch (err) {\n reject(err)\n }\n })\n }\n\n ubre.handle = (request, fn) => {\n typeof request === 'object'\n ? Object.keys(request).forEach(h => ubre.handle(h, request[h]))\n : handlers.set(request, fn)\n }\n\n ubre.open = () => {\n open = true\n\n subscriptions.forEach((s, topic) => s.forEach(m => !m.sent && (\n forward({ subscribe: topic, body: m.body }, m.target),\n m.sent = true\n )))\n\n requests.forEach((r, id) => !r.sent && (\n forward({ request: r.request, id, body: r.body }, r.target),\n r.sent = true\n ))\n\n queue.forEach(({ id, message }, from) =>\n sendResponse(from, id, message)\n )\n\n publishes.forEach((target, p) => {\n forward(p, target)\n publishes.delete(p)\n })\n }\n\n ubre.close = function() {\n if (this.target) {\n subscribers.removeItems(this.target)\n subscriptions.forEach(s => s.forEach(({ target }) =>\n target === this.target && s.delete(target)\n ))\n requests.forEach((r, id) => r.target === this.target && (\n r.reject(new Error('closed')),\n requests.delete(id)\n ))\n responses.delete(this.target)\n queue.delete(this.target)\n } else {\n open = false\n subscriptions.forEach(s => s.forEach(m => m.sent = false))\n }\n }\n\n receive(ubre.message)\n\n return ubre\n}\n\nfunction MapSet() {\n const map = Map()\n\n return {\n add: (key, item) => (map.get(key) || map.set(key, Set()).get(key)).add(item),\n has: map.has.bind(map),\n get: map.get.bind(map),\n delete: map.delete.bind(map),\n clear: map.clear.bind(map),\n forEach: map.forEach.bind(map),\n removeItems: item => map.forEach(set => set.delete(item)),\n remove: (key, item) => {\n if (!map.has(key))\n return\n\n const set = map.get(key)\n set.delete(item)\n set.size === 0 && map.delete(key)\n }\n }\n}\n\nfunction Map() {\n let keys = []\n , values = []\n\n const map = {\n has: x => keys.indexOf(x) !== -1,\n get: x => values[keys.indexOf(x)],\n set: (x, v) => (keys.push(x), values.push(v), map),\n forEach: fn => keys.forEach((k, i) => fn(values[i], k, map)),\n clear: () => (keys = [], values = [], undefined),\n delete: x => {\n const index = keys.indexOf(x)\n if (index > -1) {\n keys.splice(index, 1)\n values.splice(index, 1)\n }\n }\n }\n\n return map\n}\n\nfunction Set() {\n let values = []\n\n const set = {\n add: x => (values.indexOf(x) === -1 && values.push(x), set),\n clear: () => (values = [], undefined),\n delete: x => values.indexOf(x) !== -1 ? (values.splice(values.indexOf(x), 1), true) : false,\n forEach: fn => values.forEach(v => fn(v, v, set)),\n has: x => values.indexOf(x) !== -1\n }\n\n Object.defineProperty(set, 'size', {\n get() {\n return values.length\n }\n })\n\n return set\n}\n\nfunction copy(o, seen = []) {\n return Object.keys(o).reduce((acc, key) => (\n acc[key] = o[key] && typeof o[key] === 'object'\n ? (\n seen.push(o),\n seen.indexOf(o[key]) > -1\n ? '[Circular]'\n : copy(o[key], seen))\n : o[key],\n acc\n ), Array.isArray(o) ? [] : {})\n}\n\nconst common = ['name', 'message', 'stack', 'code']\nfunction unwrapErr(error) {\n if (typeof error === 'function')\n return '[Function: ' + (error.name || 'anonymous') + ']'\n\n if (typeof error !== 'object')\n return error\n\n const err = copy(error)\n common.forEach(c =>\n typeof error[c] === 'string' && (err[c] = error[c])\n )\n return err\n}\n\n","import Ubre from './ubre.js'\n\nexport function client(ws, options) {\n options = options || {}\n if (typeof options.send !== 'function')\n options.send = message => ws.send(message)\n\n const ubre = Ubre(options)\n\n ws.addEventListener('message', (e) => e.data && e.data[0] === '{' && ubre.message(e.data, ws))\n ws.addEventListener('open', () => ubre.open())\n ws.addEventListener('close', () => ubre.close())\n\n ubre.ws = ws\n\n return ubre\n}\n\nexport function server(server, options) {\n options = options || {}\n if (typeof options.send !== 'function')\n options.send = (message, ws) => ws.send(message)\n\n if (!('open' in options))\n options.open = true\n\n const ubre = Ubre(options)\n ubre.wss = server\n server.on('connection', ws => {\n ws.on('message', data => data[0] === '{' && ubre.message(data, ws))\n ws.on('close', () => ubre(ws).close())\n })\n\n return ubre\n}\n","import Ubre from './ubre.js'\nimport { client, server } from './websocket.js'\n\nUbre.ws = client\nUbre.wss = server\n\nexport default Ubre\n","import Pws from 'pws'\nimport Ubre from 'ubre'\n\nconst protocol = location.protocol === 'https:' ? 'wss' : 'ws'\n , socket = new Pws(protocol + '://' + location.host + '/wright')\n , ubre = Ubre.ws(socket)\n\nubre.subscribe('reload', () => !window.wright && location.reload())\nubre.subscribe('run', ({ method, arg }) => {\n const fn = method.split('.').reduce((acc, m) => acc[m] || {}, window)\n typeof fn === 'function'\n ? fn(arg)\n : ubre.publish('error', { message: 'Couldn\\'t find window.' + method })\n})\n\nlet opened = false\n\nsocket.addEventListener('open', () => {\n opened && location.reload()\n opened = true\n})\n\nexport default ubre\n","import ubre from './ubre'\n\nwindow.onerror = function(msg, file, line, col, err) { // eslint-disable-line\n err = (!err || typeof err === 'string') ? { message: msg || err } : err\n\n err.stack = (!err.stack || String(err) === err.stack)\n ? ('at ' + (file || 'unknown') + ':' + line + ':' + col)\n : err.stack\n\n ubre.publish('error', {\n message: err.message || String(err),\n error: err,\n stack: err.stack\n })\n}\n","const el = document.getElementById('wrightSocket')\nel && el.parentNode.removeChild(el)\n\nimport './p.js'\nimport './ubre.js'\n// import './inspect.js'\nimport './catcher.js'\n"],"names":["const","let","this","Pws"],"mappings":"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"} -------------------------------------------------------------------------------- /lib/chrome/index.js: -------------------------------------------------------------------------------- 1 | const wait = require('./wait') 2 | , tabs = require('./tabs') 3 | , probe = require('./probe') 4 | , launch = require('./launch') 5 | , websockets = require('./websockets') 6 | 7 | module.exports = websockets 8 | 9 | module.exports.start = function() { 10 | 11 | return Promise.resolve() 12 | .then(probe) 13 | .then(launch) 14 | .then(wait) 15 | .then(tabs) 16 | .then(websockets.connect) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /lib/chrome/launch.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const log = require('../log') 4 | , config = require('../config') 5 | , cp = require('child_process') 6 | , fs = require('fs') 7 | 8 | module.exports = function() { 9 | const path = getPath() 10 | 11 | if (!fs.existsSync(path)) { 12 | console.error('\nCan\'t find Chrome at:\n' + path + '\n') // eslint-disable-line 13 | console.error('If Chrome is installed somewhere else, set the environment variable CHROME_PATH\n') // eslint-disable-line 14 | return 15 | } 16 | 17 | const args = [ 18 | '--no-first-run', 19 | '--no-default-browser-check', 20 | '--restore-last-session', 21 | '--disable-web-security', 22 | '--test-type', // Remove warning banner from --disable-web-security usage 23 | '--disable-translate', 24 | '--disable-features=TranslateUI', 25 | '--disable-infobars', 26 | '--user-data-dir=' + config.appData, 27 | '--remote-debugging-port=' + (config.debugPort) 28 | ] 29 | 30 | if (config.debug === 2) 31 | args.push('--enable-logging=stderr') 32 | 33 | if (config.fps) 34 | args.push('--show-fps-counter') 35 | 36 | const child = cp.spawn(path, args) 37 | 38 | child.on('error', err => { 39 | if (err.code === 'ENOENT') 40 | log.error('It seems the path to chrome is wrong.\n\nCheck if chrome exists at: "' + err.path + '"') 41 | else 42 | log.error(err) 43 | 44 | process.exit() // eslint-disable-line 45 | }) 46 | 47 | if (config.debug > 1) { 48 | let lastStdErr = Date.now() 49 | 50 | child.stderr.on('data', d => { 51 | if (Date.now() - lastStdErr > 10) 52 | log('\x1b[32mChrome err:\x1b[0m', d.toString()) 53 | else 54 | log.console.log(d.toString()) 55 | 56 | lastStdErr = Date.now() 57 | }) 58 | } 59 | 60 | if (config.debug > 2) { 61 | let lastStdOut = Date.now() 62 | 63 | child.stdout.on('data', d => { 64 | if (Date.now() - lastStdOut > 10) 65 | log('\x1b[32mChrome out:\x1b[0m', d.toString()) 66 | else 67 | log.console.log(d.toString()) 68 | 69 | lastStdOut = Date.now() 70 | }) 71 | } 72 | 73 | } 74 | 75 | function getPath() { 76 | if (process.env.CHROME_PATH) // eslint-disable-line 77 | return process.env.CHROME_PATH.trim() // eslint-disable-line 78 | 79 | if (process.platform === 'darwin') { 80 | return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' 81 | } else if (process.platform === 'linux') { 82 | return cp.execSync('which google-chrome || which chromium || echo', { encoding: 'utf8' }).trim() 83 | || '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe' 84 | } else if (process.platform === 'win32') { 85 | return [ 86 | process.env['LOCALAPPDATA'] + '\\Google\\Chrome\\Application\\chrome.exe', // eslint-disable-line 87 | process.env['PROGRAMFILES'] + '\\Google\\Chrome\\Application\\chrome.exe', // eslint-disable-line 88 | process.env['PROGRAMFILES(X86)'] + '\\Google\\Chrome\\Application\\chrome.exe' // eslint-disable-line 89 | ].find(fs.existsSync) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/chrome/probe.js: -------------------------------------------------------------------------------- 1 | const config = require('../config') 2 | , session = require('../session') 3 | , utils = require('../utils') 4 | 5 | module.exports = function() { 6 | 7 | return utils.request(config.debugUrl + '/json/list/') 8 | .then(tabs => { 9 | if (!Array.isArray(tabs)) 10 | throw new Error('Not a chrome response') 11 | }) 12 | .catch(() => { 13 | session.set('scripts', []) 14 | session.set('assets', []) 15 | return utils.nextFreePort(config.debugPort) 16 | .then(port => { 17 | if (port) { 18 | config.debugPort = port 19 | config.debugUrl = 'http://127.0.0.1:' + config.debugPort 20 | } 21 | }) 22 | }) 23 | .then(() => session.set('debugPort', config.debugPort)) 24 | } 25 | -------------------------------------------------------------------------------- /lib/chrome/tabs.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const utils = require('../utils') 4 | , url = require('url') 5 | , config = require('../config') 6 | , session = require('../session') 7 | 8 | module.exports = () => 9 | 10 | utils.request(config.debugUrl + '/json/list/').then(tabs => { 11 | 12 | tabs.filter(t => t.url.startsWith('chrome://')).forEach(t => 13 | utils.request(config.debugUrl + '/json/close/' + t.id) 14 | ) 15 | 16 | closeNewTab() 17 | 18 | const tab = tabs.find(t => 19 | url.parse(config.url).port === url.parse(t.url).port && 20 | url.parse(config.url).hostname === url.parse(t.url).hostname 21 | ) 22 | 23 | if (tab) 24 | return tab 25 | 26 | session.set('scripts', []) 27 | return utils.request(config.debugUrl + '/json/new?' + config.url) 28 | }).then(tab => { 29 | return { url: tab.webSocketDebuggerUrl } 30 | }) 31 | 32 | function closeNewTab() { 33 | const interval = setInterval(() => { 34 | utils.request(config.debugUrl + '/json/list/').then(tabs => { 35 | const newtab = tabs.find(t => t.url === 'chrome://newtab/') 36 | return newtab && utils.request(config.debugUrl + '/json/close/' + newtab.id) 37 | }).catch(() => { 38 | // noop 39 | }) 40 | }, 50) 41 | 42 | setTimeout(() => clearInterval(interval), 5000) 43 | } 44 | -------------------------------------------------------------------------------- /lib/chrome/wait.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const utils = require('../utils') 4 | , log = require('../log') 5 | , config = require('../config') 6 | 7 | module.exports = function() { 8 | log.debug('Waiting for chrome to launch') 9 | return utils.retryConnect(config.debugUrl + '/json/list', 30000) 10 | } 11 | -------------------------------------------------------------------------------- /lib/chrome/websockets.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const WebSocket = require('ws') 4 | , PersistentWebSocket = require('pws') 5 | , log = require('../log') 6 | , EventEmitter = require('events') 7 | , config = require('../config') 8 | 9 | const chrome = new EventEmitter() 10 | , promises = new Map() 11 | , startId = 1 12 | 13 | module.exports = chrome 14 | 15 | let id = startId 16 | , ws 17 | 18 | chrome.send = function(method, params) { 19 | if (config.browser !== 'chrome') 20 | return 21 | 22 | return new Promise((resolve, reject) => { 23 | if (!chrome.ws || chrome.ws.readyState !== WebSocket.OPEN) 24 | return reject(new Error('Not connected to chrome debugger')) 25 | 26 | const message = { 27 | id: id++, 28 | method: method 29 | } 30 | 31 | if (params) 32 | message.params = params 33 | 34 | chrome.ws.send(JSON.stringify(message)) 35 | promises.set(message.id, { resolve: resolve, reject: reject }) 36 | }) 37 | } 38 | 39 | const clientSrc = 'if(window.self === window.top) window.wright = {};' 40 | 41 | chrome.connect = function(tab) { 42 | return new Promise((resolve, reject) => { 43 | if (ws) 44 | ws.close() 45 | 46 | ws = new PersistentWebSocket(tab.url, { 47 | pingTimeout: 0, 48 | maxTimeout: 1000 * 10 49 | }, WebSocket) 50 | 51 | chrome.ws = ws 52 | 53 | ws.onopen = () => { 54 | 55 | resolve(chrome) 56 | chrome.send('Page.disable') 57 | .then(() => chrome.send('Page.enable')) 58 | .then(() => chrome.send('Debugger.enable')) 59 | .then(() => chrome.send('DOM.enable')) 60 | .then(() => chrome.send('CSS.enable')) 61 | .then(() => chrome.send('Runtime.enable')) 62 | .then(() => chrome.send('Runtime.evaluate', { expression: clientSrc })) 63 | .then(() => chrome.send('Page.addScriptToEvaluateOnLoad', { scriptSource: clientSrc })) 64 | .then(() => chrome.send('Network.setCacheDisabled', { cacheDisabled: true } )) 65 | .then(() => chrome.emit('Started')) 66 | .catch(log.error) 67 | } 68 | 69 | ws.onerror = event => { 70 | log.debug('chrome ws', event.type, event.code || event.message) 71 | reject() 72 | } 73 | 74 | ws.onclose = (e) => { 75 | ws.retries < 3 76 | ? log.error('Connection to Chrome debugger closed - reconnecting') 77 | : process.exit() 78 | } 79 | 80 | ws.onmessage = message => { 81 | const data = JSON.parse(message.data) 82 | 83 | if (data.method) 84 | chrome.emit(data.method, data.params) 85 | 86 | const promise = promises.get(data.id) 87 | 88 | if (data.id && data.id >= startId && promise) { 89 | if (data.error) 90 | promise.reject(data.error) 91 | else 92 | promise.resolve(data.result) 93 | 94 | promises.delete(data.id) 95 | } 96 | } 97 | }) 98 | } 99 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const url = require('url') 4 | , log = require('./log') 5 | , path = require('path') 6 | , utils = require('./utils') 7 | , session = require('./session') 8 | , childProcess = require('child_process') 9 | 10 | const config = module.exports 11 | 12 | Object.assign(config, { 13 | name : null, 14 | browser : 'chrome', 15 | env : null, 16 | main : null, 17 | run : null, 18 | serve : null, 19 | assets : [], 20 | execute : null, 21 | watch : null, 22 | js : null, 23 | css : null, 24 | port : null, 25 | jail : true, 26 | debug : false, 27 | fps : false, 28 | clone : false, 29 | watchThrottle: null 30 | }) 31 | 32 | config.set = function(options) { 33 | if (options.env) 34 | process.env.NODE_ENV = options.env 35 | 36 | options = options || {} 37 | 38 | Object.keys(options).forEach(key => { 39 | if (config.hasOwnProperty(key) && key in options) 40 | config[key] = options[key] 41 | else 42 | throw new Error('The key ' + key + ' is not supported') 43 | }) 44 | 45 | if (config.watchThrottle === true) 46 | config.watchThrottle = 100 47 | 48 | if (!config.name) 49 | config.name = path.basename(process.cwd()) 50 | 51 | config.id = utils.getId(config.name) 52 | 53 | config.run = config.run === true ? 'run' : config.run 54 | config.jail = Boolean(config.run) && config.jail 55 | 56 | if (config.main && config.main.match('^https?://')) 57 | config.external = config.main 58 | else if (config.main) 59 | config.main = path.isAbsolute(config.main) ? config.main : path.join(process.cwd(), config.main) 60 | else 61 | config.main = process.cwd() 62 | 63 | if (!Array.isArray(config.assets)) 64 | config.assets = config.assets ? [config.assets] : [] 65 | 66 | if (!config.serve) 67 | config.serve = config.external ? process.cwd() : path.dirname(config.main) 68 | 69 | if (!path.isAbsolute(config.serve)) 70 | config.serve = path.normalize(path.join(process.cwd(), config.serve)) 71 | 72 | config.appData = utils.getAppDataDirectory(process, config.id) 73 | 74 | config.execute = Array.isArray(config.execute) ? config.execute : config.execute ? [config.execute] : [] 75 | config.execute = config.execute.map(s => typeof s === 'string' ? { command: s } : s) 76 | 77 | config.js = cleanInjects(config.js, '.js', '**/*.{js,ls,purs,ts,cljs,coffee,litcoffee,jsx}') 78 | config.css = cleanInjects(config.css, '.css', '**/*.{css,styl,less,sass}') 79 | 80 | if (!config.port) 81 | config.port = session.get('port') 82 | 83 | return Promise.resolve(config.port 84 | ? utils.testPort(config.port) 85 | : utils.nextFreePort(session.portStart()) 86 | ) 87 | .then(port => { 88 | config.port = port 89 | session.set('port', config.port) 90 | config.url = utils.cleanUrl('http://localhost:' + config.port) 91 | 92 | config.debugPort = session.get('debugPort') || (config.port + 1) 93 | config.debugUrl = 'http://127.0.0.1:' + config.debugPort 94 | 95 | if (config.debug === 1) 96 | log.debug(config) 97 | }) 98 | .catch(err => { 99 | if (err.code === 'EADDRINUSE') 100 | log.error('It appears port ' + config.port + ' is in use. Are you already running this project?') 101 | process.exit() // eslint-disable-line 102 | }) 103 | } 104 | 105 | function cleanInjects(injects, ext, watch) { 106 | injects = Array.isArray(injects) ? injects : injects ? [injects] : [] 107 | injects = injects.map(inject => typeof inject === 'function' ? { compile: inject } : inject) 108 | injects = injects.map(inject => typeof inject === 'string' ? { compile: inject } : inject) 109 | injects.filter(inject => typeof inject.compile === 'string').forEach(processifyCompile) 110 | injects.filter(inject => inject.path).forEach(inject => inject.path = url.resolve('/', inject.path)) 111 | injects.filter(inject => !inject.path).forEach(setInjectPath(ext)) 112 | injects.filter(inject => !inject.watch).forEach(inject => inject.watch = watch) 113 | 114 | return injects 115 | } 116 | 117 | function setInjectPath(ext) { 118 | return (obj, i) => { 119 | obj.inject = true 120 | obj.path = '/wrightinjected' + (i || '') + ext 121 | } 122 | } 123 | 124 | function processifyCompile(obj) { 125 | const args = obj.compile 126 | 127 | obj.compile = function(callback) { 128 | childProcess.exec(args, { 129 | encoding: 'utf8', 130 | maxBuffer: 10 * 1024 * 1024 131 | }, (err, stdout, stderr) => { 132 | callback(err || stderr, stdout) 133 | }) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /lib/execute.js: -------------------------------------------------------------------------------- 1 | const config = require('./config') 2 | , log = require('./log') 3 | , path = require('path') 4 | , chokidar = require('chokidar') 5 | , childProcess = require('child_process') 6 | 7 | module.exports = function() { 8 | 9 | config.execute.forEach(execute) 10 | config.execute.filter(s => s.watch).forEach((s, i) => { 11 | chokidar.watch(s.watch, { 12 | ignoreInitial: true 13 | }).on('add', () => changed(s, i)).on('change', () => changed(s, i)) 14 | }) 15 | 16 | } 17 | 18 | function changed(obj, i) { 19 | execute(obj, i) 20 | } 21 | 22 | function prepare(cmd) { 23 | // Replace unix path space escapes (\ ) with a temp placeholder 24 | cmd = cmd.replace(/\\ /g, ':::') 25 | 26 | // Split by whitespace, preserving quoted strings (single or double) 27 | cmd = cmd.match(/(?:[^\s'"]+|'[^']*'|"[^"]*")+/g) 28 | 29 | // Replace the temp placeholder with a space and eliminate the quotes 30 | cmd = cmd.map(arg => arg.replace(/:::/g, ' ').replace(/^'(.*)'$/, '$1').replace(/^"(.*)"$/, '$1')) 31 | 32 | return cmd 33 | } 34 | 35 | async function execute(obj, i) { 36 | 37 | if (typeof obj.command === 'function') 38 | return obj.command() 39 | 40 | if (obj.process) { 41 | log.debug('Exiting', obj.command) 42 | obj.process.kill() 43 | await new Promise(r => obj.process.once('exit', r)) 44 | log.debug('Exited', obj.command) 45 | } 46 | 47 | const cmd = prepare(obj.command) 48 | 49 | log.debug('Spawning', obj.command) 50 | obj.process = childProcess.spawn( 51 | cmd.shift(), 52 | cmd, 53 | Object.assign({ 54 | shell: process.platform === 'win32' 55 | }, obj.options, { 56 | env: { 57 | NODE_ENV: 'development', 58 | ...process.env, 59 | ...(obj.options && obj.options.env || {}), 60 | PATH: path.join(process.cwd(), 'node_modules', '.bin') + ':' + process.env.PATH 61 | } 62 | }) 63 | ) 64 | 65 | obj.process.on('error', log.error) 66 | obj.process.stdout.on('data', b => log('PS' + (i + 1), b.toString().replace(/\n$/, ''))) 67 | obj.process.stderr.on('data', b => log.error('PS' + (i + 1), b.toString().replace(/\n$/, ''))) 68 | } 69 | 70 | process.on('exit', () => { 71 | (config.execute || []).filter(s => s.process).forEach(s => s.process.kill()) 72 | }) 73 | -------------------------------------------------------------------------------- /lib/firefox/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const log = require('../log') 4 | , config = require('../config') 5 | , cp = require('child_process') 6 | , fs = require('fs') 7 | 8 | module.exports = function() { 9 | const path = getPath() 10 | 11 | if (!fs.existsSync(path)) { 12 | console.error('\nCan\'t find Firefox at:\n' + path + '\n') // eslint-disable-line 13 | console.error('If firefox is installed somewhere else, set the environment variable FIREFOX_PATH\n') // eslint-disable-line 14 | return 15 | } 16 | 17 | const args = [ 18 | '-new-instance', 19 | '-P "' + config.appData + '"', 20 | '-active', 21 | '-url', config.url 22 | ] 23 | 24 | const child = cp.spawn(path, args, { detached: process.platform !== 'win32' }) 25 | 26 | child.on('error', err => { 27 | if (err.code === 'ENOENT') 28 | log.error('It seems the path to chrome is wrong.\n\nCheck if chrome exists at: "' + err.path + '"') 29 | else 30 | log.error(err) 31 | 32 | process.exit() // eslint-disable-line 33 | }) 34 | 35 | if (config.debug > 1) { 36 | let lastStdErr = Date.now() 37 | 38 | child.stderr.on('data', d => { 39 | if (Date.now() - lastStdErr > 10) 40 | log('\x1b[32mFirefox err:\x1b[0m', d.toString()) 41 | else 42 | log.console.log(d.toString()) 43 | 44 | lastStdErr = Date.now() 45 | }) 46 | } 47 | 48 | if (config.debug > 2) { 49 | let lastStdOut = Date.now() 50 | 51 | child.stdout.on('data', d => { 52 | if (Date.now() - lastStdOut > 10) 53 | log('\x1b[32mFirefox out:\x1b[0m', d.toString()) 54 | else 55 | log.console.log(d.toString()) 56 | 57 | lastStdOut = Date.now() 58 | }) 59 | } 60 | 61 | } 62 | 63 | function getPath() { 64 | if (process.env.FIREFOX_PATH) // eslint-disable-line 65 | return process.env.FIREFOX_PATH.trim() // eslint-disable-line 66 | 67 | if (process.platform === 'darwin') { 68 | return '/Applications/Firefox.app/Contents/MacOS/firefox' 69 | } else if (process.platform === 'linux') { 70 | return cp.execSync('which firefox', { encoding: 'utf8' }).trim() 71 | } else if (process.platform === 'win32') { 72 | return [ 73 | process.env['PROGRAMFILES'] + '\\Mozilla Firefox\\firefox.exe', // eslint-disable-line 74 | process.env['PROGRAMFILES(X86)'] + '\\Mozilla Firefox\\firefox.exe' // eslint-disable-line 75 | ].find(fs.existsSync) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('dotenv').config() 4 | 5 | const log = require('./log') 6 | , serve = require('./serve') 7 | , watch = require('./watch') 8 | , config = require('./config') 9 | , chrome = require('./chrome') 10 | , firefox = require('./firefox') 11 | , execute = require('./execute') 12 | 13 | let promise 14 | 15 | module.exports = function wright(options) { 16 | log.debugging = Boolean(options.debug) 17 | log('Starting wright...') 18 | 19 | if (promise) 20 | return promise 21 | 22 | promise = config.set(options) 23 | .then(execute) 24 | .then(serve.start) 25 | .then(config.browser === 'chrome' && chrome.start) 26 | .then(config.browser === 'firefox' && firefox) 27 | .then(watch) 28 | .catch(err => { 29 | log.error(err) 30 | process.exit() // eslint-disable-line 31 | }) 32 | 33 | return promise 34 | } 35 | 36 | module.exports.chrome = chrome 37 | module.exports.watch = require('./watch/watch') 38 | -------------------------------------------------------------------------------- /lib/jail.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const config = require('./config') 4 | 5 | module.exports = function(code) { 6 | return config.jail 7 | ? code.replace(/((function.*?\)|=>)\s*{)/g, '$1eval(0);') 8 | : code 9 | } 10 | -------------------------------------------------------------------------------- /lib/log.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0 */ 2 | 3 | function log() { 4 | addTime(arguments) 5 | console.log.apply(console, arguments) 6 | } 7 | 8 | log.error = function(err) { 9 | if (!err) 10 | return 11 | 12 | Array.prototype.unshift.call(arguments, '\x1b[31mError:\x1b[0m') 13 | addTime(arguments) 14 | console.error.apply(console, arguments) 15 | } 16 | 17 | log.debug = function(a, b, c) { 18 | if ((a || b || c) && log.debugging) { 19 | Array.prototype.unshift.call(arguments, '\x1b[32mDebug:\x1b[0m') 20 | log.apply(null, arguments) 21 | } 22 | } 23 | 24 | function addTime(args) { 25 | const now = new Date() 26 | , h = pad(now.getHours(), 2) 27 | , m = pad(now.getMinutes(), 2) 28 | , s = pad(now.getSeconds(), 2) 29 | , ms = pad(now.getMilliseconds(), 3) 30 | , stamp = '\x1b[2m' + h + ':' + m + ':' + s + '.' + ms + '\x1b[0m' 31 | 32 | Array.prototype.unshift.call(args, stamp) 33 | } 34 | 35 | log.console = console 36 | 37 | function pad(str, len) { 38 | str = String(str) 39 | while (str.length < len) 40 | str = '0' + str 41 | 42 | return str 43 | } 44 | 45 | module.exports = log 46 | -------------------------------------------------------------------------------- /lib/serve/client.js: -------------------------------------------------------------------------------- 1 | const ws = require('ws') 2 | , Url = require('url') 3 | , Ubre = require('ubre') 4 | , path = require('path') 5 | , config = require('../config') 6 | , log = require('../log') 7 | , utils = require('../utils') 8 | , js = require('./js') 9 | , SourceMap = require('source-map') 10 | 11 | const wss = new ws.Server({ noServer: true, path: '/wright' }) 12 | const ubre = Ubre.wss(wss) 13 | 14 | module.exports.ubre = ubre 15 | module.exports.wss = wss 16 | 17 | wss.on('connection', (socket, req) => { 18 | const ub = ubre(socket) 19 | 20 | ub.subscribe('error', err => { 21 | err.userAgent = req.headers['user-agent'] 22 | err.ip = req.connection.remoteAddress 23 | log('Client error: ', err) 24 | }) 25 | 26 | ub.subscribe('goto', ({ 27 | url, 28 | line, 29 | column 30 | }) => { 31 | const filename = Url.parse(url).pathname 32 | const file = js.sourceMaps && js.sourceMaps[filename + '.map'] 33 | if (!file) 34 | return utils.launchEditor(path.join(config.serve, filename) + ':' + line + ':' + column) 35 | 36 | SourceMap.SourceMapConsumer.with(file, null, consumer => { 37 | const result = consumer.originalPositionFor({ 38 | line: parseInt(line), 39 | column: parseInt(column) 40 | }) 41 | utils.launchEditor(result.source + ':' + result.line + ':' + result.column, config.editor) 42 | }) 43 | }) 44 | socket.on('error', () => { /* noop */ }) 45 | }) 46 | -------------------------------------------------------------------------------- /lib/serve/clone.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | , log = require('../log') 5 | , path = require('path') 6 | , config = require('../config') 7 | 8 | const cloned = new Set() 9 | 10 | module.exports = function(req, res) { 11 | if (req.method !== 'GET') 12 | return 13 | 14 | const _write = res.write 15 | , _end = res.end 16 | , _writeHead = res.writeHead 17 | 18 | let stream 19 | 20 | res.write = function(buf) { 21 | _write.apply(res, arguments) 22 | 23 | if (stream) 24 | stream.write(buf) 25 | } 26 | 27 | res.end = function() { 28 | if (stream) 29 | stream.end() 30 | 31 | _end.apply(res, arguments) 32 | } 33 | 34 | res.writeHead = function(statusCode, headers) { 35 | _writeHead.apply(res, arguments) 36 | 37 | const localPath = path.join(config.serve, req.url + (req.url.endsWith('/') ? 'index.html' : '')).split('?')[0] 38 | 39 | if (statusCode !== 200 || cloned.has(localPath)) 40 | return 41 | 42 | cloned.add(localPath) 43 | ensureDirectoryExistence(localPath) 44 | stream = fs.createWriteStream(localPath, { flags: config.clone === 'overwrite' ? 'w' : 'wx' }) 45 | stream.on('error', () => { 46 | log.error(path.relative(config.serve, localPath) + ' exists. Pass \'--clone overwrite\' to force') 47 | }) 48 | stream.on('close', () => { 49 | log.debug('cloned', path.relative(config.serve, localPath)) 50 | }) 51 | } 52 | } 53 | 54 | function ensureDirectoryExistence(filePath) { 55 | const dirname = path.dirname(filePath) 56 | 57 | if (fs.existsSync(dirname)) 58 | return true 59 | 60 | ensureDirectoryExistence(dirname) 61 | fs.mkdirSync(dirname) 62 | } 63 | -------------------------------------------------------------------------------- /lib/serve/css.js: -------------------------------------------------------------------------------- 1 | const Url = require('url') 2 | , config = require('../config') 3 | , utils = require('../utils') 4 | , log = require('../log') 5 | 6 | module.exports = (req, res, next) => { 7 | const pathname = Url.parse(req.url).pathname 8 | , css = config.css.find(f => f.path && f.path.toLowerCase() === pathname.toLowerCase()) 9 | 10 | if (!css) 11 | return next() 12 | 13 | utils 14 | .promisify(css.compile) 15 | .then(css => { 16 | res.setHeader('Content-Type', 'text/css') 17 | res.end(css) 18 | }) 19 | .catch(err => { 20 | log.error(err) 21 | res.statusCode = 500 22 | res.end(err) 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /lib/serve/html.js: -------------------------------------------------------------------------------- 1 | const config = require('../config') 2 | , fs = require('fs') 3 | , log = require('../log') 4 | 5 | let injectedJs 6 | , injectedCss 7 | , extraByteLength 8 | 9 | const injectedWright = ` 10 | ` 11 | 12 | module.exports = { 13 | init, 14 | hijack, 15 | index 16 | } 17 | 18 | function init() { 19 | const injectAll = !config.external && !config.main.endsWith('.html') 20 | 21 | injectedJs = config.js.filter(js => injectAll || js.inject).map(js => js.path).map(x => scriptTag(x)).join('') 22 | injectedCss = config.css.filter(css => injectAll || css.inject).map(css => css.path).map(linkTag).join('') 23 | extraByteLength = Buffer.byteLength(injectedCss + injectedJs + injectedWright, 'utf8') 24 | } 25 | 26 | function hijack(res) { 27 | res.setHeader('Content-Type', 'text/html; charset=utf-8') 28 | 29 | const _write = res.write 30 | , _end = res.end 31 | , _writeHead = res.writeHead 32 | 33 | let content = '' 34 | 35 | res.write = function(buf) { 36 | content += buf.toString() 37 | } 38 | 39 | res.end = function(a) { 40 | _write.call(res, addFiles(content)) 41 | _end.apply(res, arguments) 42 | } 43 | 44 | res.writeHead = function(status, headers) { 45 | headers && headers['content-length'] && (headers['content-length'] = parseInt(headers['content-length']) + extraByteLength) 46 | res.getHeader('content-length') && res.setHeader('content-length', parseInt(res.getHeader('content-length')) + extraByteLength) 47 | _writeHead.apply(res, arguments) 48 | } 49 | } 50 | 51 | function index(res) { 52 | res.setHeader('Content-Type', 'text/html; charset=utf-8') 53 | if (config.main.endsWith('.js')) 54 | return res.end(addFiles(html(config.name) + scriptTag(config.main.replace(config.serve, ''), true))) 55 | 56 | if (!config.main.endsWith('.html')) 57 | return res.end(addFiles(html(config.name))) 58 | 59 | fs.readFile(config.main, 'utf8', (err, content) => { 60 | if (err) 61 | return log.error('Error reading', config.main, err) 62 | 63 | res.end(addFiles(content)) 64 | }) 65 | } 66 | 67 | function addFiles(content) { 68 | return (content.includes(')/i, '$1' + injectedCss + injectedWright) 70 | : content.replace('>', '>' + injectedCss + injectedWright) 71 | ) + injectedJs 72 | } 73 | 74 | function html(title) { 75 | return ` 76 | 77 | Wright /${ title } 78 | 79 | ` 80 | } 81 | 82 | function scriptTag(file, module) { 83 | return '\n' 84 | } 85 | 86 | function linkTag(file) { 87 | return '\n' 88 | } 89 | -------------------------------------------------------------------------------- /lib/serve/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | , ey = require('ey') 5 | , js = require('./js') 6 | , Url = require('url') 7 | , css = require('./css') 8 | , log = require('../log') 9 | , path = require('path') 10 | , http = require('http') 11 | , html = require('./html') 12 | , clone = require('./clone') 13 | , utils = require('../utils') 14 | , client = require('./client') 15 | , config = require('../config') 16 | , assets = require('../watch/assets') 17 | , EventEmitter = require('events') 18 | , httpProxy = require('http-proxy') 19 | , ServeStatic = require('serve-static') 20 | , finalhandler = require('finalhandler') 21 | 22 | http.globalAgent.maxSockets = 10000 23 | 24 | const serve = new EventEmitter() 25 | , browserClient = fs.readFileSync(path.join(__dirname, '../browser/wright.js'), 'utf8') 26 | , browserClientMap = fs.readFileSync(path.join(__dirname, '../browser/wright.js.map'), 'utf8') 27 | 28 | module.exports = serve 29 | module.exports.ubre = client.ubre 30 | 31 | module.exports.start = function() { 32 | 33 | return new Promise((resolve, reject) => { 34 | 35 | const serveOptions = { 36 | etag : false, 37 | redirect : false, 38 | fallthrough : Boolean(config.external && !config.clone), 39 | index : config.external ? 'index.html' : false, 40 | setHeaders : (res, localPath, stat) => { 41 | assets.watch(localPath) 42 | res.setHeader('Cache-Control', 'no-store, must-revalidate') 43 | } 44 | } 45 | 46 | const externalUrl = config.external && Url.parse(config.external) 47 | const statics = ServeStatic(config.serve, serveOptions) 48 | 49 | const proxy = config.external && httpProxy.createProxyServer({ 50 | agent : http.globalAgent, 51 | target : externalUrl.protocol + '//' + externalUrl.host, 52 | autoRewrite : true, 53 | changeOrigin : true, 54 | secure : false, 55 | selfHandleResponse: true 56 | }) 57 | 58 | const corsy = httpProxy.createProxyServer({ 59 | agent : http.globalAgent, 60 | autoRewrite : true, 61 | changeOrigin : true, 62 | secure : false, 63 | followRedirects : true 64 | }) 65 | 66 | proxy && proxy.on('error', error) 67 | corsy && corsy.on('error', error) 68 | 69 | proxy && proxy.on('proxyRes', function(proxyRes, req, res) { 70 | if (req.method === 'GET' && proxyRes.statusCode === 404) { 71 | return statics(req, res, () => { 72 | if (req.headers.accept && req.headers.accept.includes('html')) { 73 | req.url = '/index.html' 74 | statics(req, res, finalhandler(req, res)) 75 | } else { 76 | res.writeHead(proxyRes.statusCode, proxyRes.headers) 77 | proxyRes.pipe(res) 78 | } 79 | }) 80 | } 81 | 82 | res.writeHead(proxyRes.statusCode, proxyRes.headers) 83 | proxyRes.pipe(res) 84 | }) 85 | 86 | function error(err, req, res) { 87 | log.error(err) 88 | if (res.writeHead) { 89 | res.writeHead(500, { 'Content-Type': 'text/plain' }) 90 | res.end(String(err)) 91 | } else { 92 | res.destroy() 93 | } 94 | } 95 | 96 | function type(type) { 97 | return function(req, res, next) { 98 | res.setHeader('Content-Type', type + ';charset=utf-8') 99 | next() 100 | } 101 | } 102 | 103 | corsy.on('proxyRes', (proxyRes) => { 104 | proxyRes.headers['Access-Control-Allow-Origin'] = '*' 105 | proxyRes.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept' 106 | }) 107 | 108 | const app = ey() 109 | 110 | app.use((req, res, next) => { 111 | delete req.headers['accept-encoding'] // Avoid gzip so that wright can manipulate requests 112 | next() 113 | }) 114 | 115 | app.get('/wright.js', type('text/javascript'), (req, res) => res.end(browserClient)) 116 | app.get('/wright.js.map', (req, res) => res.end(browserClientMap)) 117 | app.all(/^\/https?:\/\//i, (req, res) => corsy.web(req, res, { target: req.url.slice(1) })) 118 | 119 | app.get(js.compile) 120 | app.get(js.rewrite) 121 | app.get(js.sourceMap) 122 | app.get(/\.css$/, css) 123 | 124 | config.external && app.get((req, res, next) => { 125 | req.method === 'GET' && req.headers.accept && req.headers.accept.includes('html') && html.hijack(res) 126 | next() 127 | }) 128 | 129 | !config.external && app.get(statics) 130 | app.get(...config.assets.map(p => ServeStatic(p, serveOptions))) 131 | app.use('/node_modules', ServeStatic(path.join(process.cwd(), 'node_modules'), serveOptions)) 132 | 133 | app.get(js.modules) 134 | 135 | config.external && app.get((req, res, next) => { 136 | config.clone && clone(req, res) 137 | proxy.web(req, res) 138 | }) 139 | 140 | config.external 141 | ? app.use((req, res, next) => proxy.web(req, res)) 142 | : app.use((req, res, next) => { 143 | req.headers.accept && req.headers.accept.indexOf('html') > -1 144 | ? html.index(res) 145 | : finalhandler(req, res)() 146 | }) 147 | 148 | const server = http.createServer(app) 149 | 150 | server.on('upgrade', (req, socket, head) => { 151 | if (req.url === '/wright') 152 | return client.wss.handleUpgrade(req, socket, head, ws => client.wss.emit('connection', ws, req)) 153 | 154 | if (config.external) 155 | return proxy.ws(req, socket, head) 156 | 157 | socket.end() 158 | }) 159 | 160 | server.on('listening', () => { 161 | html.init() 162 | resolve() 163 | }) 164 | server.on('error', reject) 165 | server.listen(config.port) 166 | }) 167 | .then(() => config.external && utils.retryConnect(config.external, 5000)) 168 | .then(() => log('Server ' + (config.external ? 'proxying' : 'started') + ' on ' + config.url)) 169 | } 170 | -------------------------------------------------------------------------------- /lib/serve/js.js: -------------------------------------------------------------------------------- 1 | const Url = require('url') 2 | , path = require('path') 3 | , config = require('../config') 4 | , utils = require('../utils') 5 | , log = require('../log') 6 | , assets = require('../watch/assets') 7 | , jail = require('../jail') 8 | , fs = require('fs') 9 | , cp = require('child_process') 10 | 11 | const sourceMaps = {} 12 | , staticImportRegex = new RegExp('((?:import|export)\\s*[{}0-9a-zA-Z*,\\s]*\\s*(?: from |)[\'"])([a-zA-Z1-9@][a-zA-Z0-9@/._-]*)([\'"])', 'g') // eslint-disable-line 13 | , dynamicImportRegex = new RegExp('([^$.]import\\(\\s?[\'"])([a-zA-Z1-9@][a-zA-Z0-9@\\/._-]*)([\'"]\\s?\\))', 'g') 14 | , moduleRegex = /(?:(?:import|export)\s+(?:[\s\S]+?\s+from\s+)?["']\S+?["'])|(?:export\s+(?:default|function|class|var|const|let|async)\s)|(?:import\s*\(\s*[`'"])(?=(?:[^"']*["'][^"']*["'])*[^"']*$)/ // eslint-disable-line 15 | , commentsRegex = /\/\*[\s\S]*?\*\/|\/\/[\s\S]*?(?:\n|$)/g 16 | 17 | const isModule = s => moduleRegex.test(s.replace(commentsRegex, '')) 18 | 19 | module.exports = { 20 | compile, 21 | rewrite, 22 | sourceMap, 23 | sourceMaps, 24 | modules 25 | } 26 | 27 | function compile(req, res, next) { 28 | const pathname = Url.parse(req.url).pathname 29 | const js = config.js.find(f => f.path && f.path.toLowerCase() === pathname.toLowerCase()) 30 | if (!js) 31 | return next() 32 | 33 | utils.promisify(js.compile).then(content => { 34 | if (!content || (typeof content !== 'string' && typeof content.code !== 'string')) 35 | throw new Error('The compile function should resolve with a code string or a { code, map } object') 36 | 37 | if (content.map) { 38 | sourceMaps[js.path + '.map'] = content.map 39 | res.setHeader('SourceMap', js.path + '.map') 40 | } 41 | 42 | res.setHeader('Content-Type', 'application/javascript') 43 | res.end(jail(content.code 44 | ? content.code + '\n//# sourceMappingURL=' + js.path + '.map' 45 | : (content.code || content))) 46 | }).catch((err) => { 47 | log.error(err) 48 | res.statusCode = 500 49 | res.end(err) 50 | }) 51 | } 52 | 53 | function sourceMap(req, res, next) { 54 | req.url in sourceMaps 55 | ? res.end(JSON.stringify(sourceMaps[req.url])) 56 | : next() 57 | } 58 | 59 | function modules(req, res, next) { 60 | if (req.url.indexOf('/node_modules/') !== 0) 61 | return next() 62 | 63 | const pkg = parseNpm(req.url.slice('/node_modules/'.length)) 64 | 65 | getStat(pkg.root) 66 | .catch(() => npmInstall(pkg)) 67 | .then(() => 68 | getStat(pkg.path) 69 | .catch(() => { 70 | pkg.path += '.js' 71 | return getStat(pkg.path) 72 | }) 73 | .catch(() => { 74 | pkg.path = path.join(pkg.path.slice(0, -3), 'index.js') 75 | return getStat(pkg.path) 76 | }) 77 | ) 78 | .then(stat => 79 | stat.isFile() 80 | ? pkg.path 81 | : resolveEntry(pkg.path).then(x => path.join(...x.split('/'))) 82 | ) 83 | .then(location => { 84 | res.statusCode = 302 85 | // isJS['/' + location] = true 86 | res.setHeader('Location', '/' + location.split(path.sep).join('/')) 87 | res.end() 88 | }) 89 | .catch((err) => { 90 | res.statusCode = 500 91 | log('Error loading package', err) 92 | res.end('Wright could not autoload ' + pkg.name) 93 | }) 94 | } 95 | 96 | function getStat(path) { 97 | return new Promise((resolve, reject) => { 98 | fs.stat(path, (err, stat) => { 99 | err ? reject(err) : resolve(stat) 100 | }) 101 | }) 102 | } 103 | 104 | function rewrite(req, res, next) { 105 | const ext = path.extname(req.pathname) 106 | if (ext === '.css' || (!req.url.startsWith('/node_modules/') && ext !== '.js' && ext !== '.mjs')) 107 | return next() 108 | 109 | const filePath = path.join(req.url.startsWith('/node_modules/') 110 | ? process.cwd() 111 | : config.serve, Url.parse(req.url).pathname) 112 | 113 | fs.readFile(filePath, 'utf8', (err, content) => { 114 | if (err || !isModule(content)) 115 | return next() 116 | 117 | assets.watch(filePath) 118 | res.setHeader('Content-Type', 'text/javascript') 119 | res.end(rewritePath(jail(content))) 120 | }) 121 | } 122 | 123 | function rewritePath(content) { 124 | return content 125 | .replace(staticImportRegex, (_, a, b, c) => { 126 | // isJS['/node_modules/' + b] = true 127 | return a + '/node_modules/' + b + c 128 | }) 129 | .replace(dynamicImportRegex, (_, a, b, c) => { 130 | // isJS['/node_modules/' + b] = true 131 | return a + '/node_modules/' + b + c 132 | }) 133 | } 134 | 135 | function resolveEntry(p) { 136 | return new Promise((resolve, reject) => { 137 | fs.readFile(path.join(p, 'package.json'), 'utf8', (err, content) => 138 | err ? reject(err) : resolve(content) 139 | ) 140 | }) 141 | .catch(() => { 142 | const newPath = path.dirname(p) 143 | if (newPath === p) 144 | throw new Error('package.json not found') 145 | 146 | return resolveEntry(newPath) 147 | }) 148 | .then(JSON.parse) 149 | .then(x => p + '/' + (x.module || x.unpkg || x.main || 'index.js')) 150 | } 151 | 152 | function npmInstall(pkg) { 153 | return new Promise(resolve => { 154 | log.debug(pkg.name + ' not found. Running npm install ' + pkg.install) 155 | cp.exec('npm install ' + pkg.install, { encoding: 'utf8' }, resolve) 156 | }) 157 | } 158 | 159 | function parseNpm(n) { 160 | const parts = n.split('/') 161 | , scoped = n[0] === '@' 162 | , install = parts.slice(0, scoped ? 2 : 1).join('/') 163 | , name = install.replace(/(.+)@.*/, '$1') 164 | 165 | return { 166 | name, 167 | install, 168 | root: path.join('node_modules', ...name.split('/')), 169 | path: path.join('node_modules', ...name.split('/'), ...parts.slice(scoped ? 2 : 1)) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /lib/session.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | , log = require('./log') 5 | , path = require('path') 6 | , utils = require('./utils') 7 | , config = require('./config') 8 | 9 | const sessionPath = path.join(utils.wrightDataDirectory(process).node, 'wright.session.json') 10 | 11 | module.exports.get = function(key) { 12 | const sessions = read() 13 | 14 | return sessions[config.id] ? sessions[config.id][key] : undefined 15 | } 16 | 17 | module.exports.set = function(key, value) { 18 | save(key, value) 19 | } 20 | 21 | module.exports.portStart = function() { 22 | const sessions = read() 23 | , highestPort = Object.keys(sessions).map(key => sessions[key].port).sort().pop() 24 | 25 | return highestPort ? (highestPort + 2) : 3000 26 | } 27 | 28 | function read() { 29 | try { 30 | return JSON.parse(fs.readFileSync(sessionPath, 'utf8')) || {} 31 | } catch (err) { 32 | return {} 33 | } 34 | } 35 | 36 | module.exports.read = read 37 | 38 | function save(key, value) { 39 | try { 40 | const session = read() 41 | 42 | if (!session[config.id]) 43 | session[config.id] = {} 44 | 45 | session[config.id][key] = value 46 | fs.writeFileSync(sessionPath, JSON.stringify(session), 'utf8') 47 | } catch (err) { 48 | log.error('Error writing session', err) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | /* eslint no-process-env: 0 */ 2 | 3 | 'use strict' 4 | 5 | const fs = require('fs') 6 | , os = require('os') 7 | , url = require('url') 8 | , net = require('net') 9 | , path = require('path') 10 | , log = require('./log') 11 | , cp = require('child_process') 12 | , crypto = require('crypto') 13 | 14 | const utils = module.exports 15 | 16 | utils.req = function(url) { 17 | return url.indexOf('https') === 0 18 | ? require('https') 19 | : require('http') 20 | } 21 | 22 | const editors = ({ 23 | darwin: { 24 | sublime: { 25 | path: '/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl', 26 | args: target => target 27 | }, 28 | code: { 29 | path: '/Applications/Visual Studio Code.app/Contents/MacOS/Electron', 30 | args: target => ['--goto', target] 31 | }, 32 | atom: { 33 | path: '/Applications/Atom.app/Contents/MacOS/Atom', 34 | args: target => target 35 | } 36 | }, 37 | win32: { 38 | sublime: { 39 | path: '%ProgramFiles%\\Sublime Text 3\\sublime_text.exe', 40 | args: target => target 41 | }, 42 | code: { 43 | path: '%LOCALAPPDATA%\\Programs\\Microsoft VS Code\\Code.exe', 44 | args: target => ['--goto', target] 45 | }, 46 | atom: { 47 | path: '%LOCALAPPDATA%\\atom\\atom.exe', 48 | args: target => target 49 | } 50 | }, 51 | linux: { 52 | 53 | } 54 | })[os.platform()] 55 | 56 | utils.launchEditor = function(target, name = guessEditor()) { 57 | const editor = editors[name] 58 | if (editor && !fs.existsSync(editor.path)) 59 | return log('Could not find editor', name, 'at', editor.path) 60 | 61 | cp.spawn(editor.path, [].concat(editor.args(target)), { 62 | stdio: 'ignore', 63 | detached: true 64 | }).unref() 65 | } 66 | 67 | function guessEditor() { 68 | return Object.keys(editors).find(editor => 69 | fs.existsSync(editors[editor].path) 70 | ) 71 | } 72 | 73 | utils.request = function(url) { 74 | return new Promise((resolve, reject) => { 75 | const req = utils.req(url).get(url, res => { 76 | let data = '' 77 | 78 | res.on('data', chunk => data += chunk) 79 | res.on('end', () => { 80 | if (res.statusCode === 200) { 81 | try { 82 | resolve(JSON.parse(data)) 83 | } catch (e) { 84 | resolve(data) 85 | } 86 | } else { 87 | reject(new Error(data)) 88 | } 89 | }) 90 | res.on('error', reject) 91 | }).on('error', reject) 92 | 93 | setTimeout(() => { 94 | req.abort() 95 | reject() 96 | }, 2000) 97 | }) 98 | } 99 | 100 | utils.getId = function(name) { 101 | return path.basename(path.join(process.cwd(), '..')) + 102 | '-' + name + '_' + 103 | crypto.createHash('sha1').update(process.cwd()).digest('hex').slice(0, 7) 104 | } 105 | 106 | utils.retryConnect = function(url, timeout) { 107 | return new Promise((resolve, reject) => { 108 | let timedOut = false 109 | 110 | setTimeout(() => { 111 | timedOut = true 112 | reject('Could not connect to ' + url) 113 | }, timeout) 114 | 115 | function connect() { 116 | utils.req(url).get(url, resolve).on('error', () => !timedOut && setTimeout(connect, 200)) 117 | } 118 | 119 | connect() 120 | }) 121 | } 122 | 123 | utils.promisify = function(fn) { 124 | return new Promise((resolve, reject) => { 125 | try { 126 | const result = fn((err, result) => err ? reject(err) : resolve(result)) 127 | 128 | if (result && result.then) 129 | result.then(resolve).catch(reject) 130 | } catch (err) { 131 | reject(err) 132 | } 133 | }) 134 | } 135 | 136 | utils.wrightDataDirectory = function(process) { 137 | const home = process.env.HOMEPATH || process.env.HOME || '' 138 | 139 | const wslPath = 140 | [process.env] 141 | .filter( x => x.WSL_DISTRO_NAME ) 142 | .reduce((acc, x) => acc.concat(x.PATH ? [x.PATH] : []), []) 143 | 144 | const wslUsername = 145 | wslPath 146 | .reduce((acc, x) => acc.concat(x.split(':')), []) 147 | .reduce((acc, x) => acc.concat(x.split('/mnt/c/Users').slice(1)), []) 148 | .reduce((acc, x) => acc.concat(x.split('/').slice(1, 2)), []) 149 | 150 | const ubuntuOnWindowsPath = 151 | wslUsername 152 | .map( 153 | username => ({ 154 | node: '/mnt/c/Users/' + username + '/.wright', 155 | chrome: 'C:\\Users\\' + username + '\\.wright' 156 | }) 157 | ) 158 | 159 | const otherwise = 160 | [home] 161 | .map( x => path.join(x, '.wright') ) 162 | .map( x => ({ node:x, chrome: x }) ) 163 | 164 | return [].concat(ubuntuOnWindowsPath, otherwise) 165 | .slice(0, 1) 166 | .concat({ node: '.wright', chrome: '.wright' }) 167 | .shift() 168 | } 169 | 170 | utils.getAppDataDirectory = function(process, name) { 171 | const wrightPath = utils.wrightDataDirectory(process) 172 | , projectPath = path.join(wrightPath.chrome, name) 173 | 174 | try { 175 | fs.mkdirSync(wrightPath.node) 176 | } catch (_) { 177 | // don't care for errors (they exist) 178 | } 179 | 180 | try { 181 | fs.mkdirSync(projectPath) 182 | } catch (_) { 183 | // don't care for errors (they exist) 184 | } 185 | 186 | return projectPath 187 | } 188 | 189 | utils.cleanUrl = function(u) { 190 | const obj = url.parse(u) 191 | 192 | if (obj.port == 80) // eslint-disable-line 193 | obj.host = obj.hostname 194 | 195 | return url.format(obj) 196 | } 197 | 198 | utils.slash = function(string) { 199 | const isExtendedLengthPath = /^\\\\\?\\/.test(string) 200 | , hasNonAscii = /[^\x00-\x80]+/.test(string) 201 | 202 | if (isExtendedLengthPath || hasNonAscii) 203 | return string 204 | 205 | return string.replace(/\\/g, '/') 206 | } 207 | 208 | utils.nextFreePort = function(port) { 209 | return new Promise((resolve, reject) => recursivePortTest(port, resolve)) 210 | } 211 | 212 | function recursivePortTest(port, resolve) { 213 | utils.testPort(port) 214 | .then(p => resolve(p)) 215 | .catch(() => recursivePortTest(port + 3, resolve)) 216 | } 217 | 218 | utils.testPort = function(port, resolve) { 219 | const tester = net.createServer() 220 | 221 | return new Promise((resolve, reject) => { 222 | tester 223 | .once('error', reject) 224 | .once('listening', () => { 225 | tester.once('close', () => { 226 | let connected = false 227 | 228 | const client = net.connect(port, () => { 229 | connected = true 230 | client.destroy() 231 | }) 232 | .once('error', (err) => connected = err.code !== 'ECONNREFUSED') 233 | .once('close', () => connected ? reject() : resolve(port)) 234 | }).close() 235 | }) 236 | .listen(port) 237 | }) 238 | } 239 | -------------------------------------------------------------------------------- /lib/watch/assets.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const js = require('./js') 4 | , css = require('./css') 5 | , path = require('path') 6 | , log = require('../log') 7 | , watch = require('./watch') 8 | , chrome = require('../chrome') 9 | , config = require('../config') 10 | 11 | module.exports.watch = function(localPath) { 12 | if (config.run && localPath.endsWith('.js')) 13 | return 14 | 15 | watch(localPath, file => { 16 | 17 | if (file.endsWith('.html')) 18 | return refresh(file) 19 | 20 | log.debug('Reloading', file) 21 | 22 | updateUrlInStyles(path.join(process.cwd(), file)) 23 | .then(() => updateSrc(file)) 24 | .then(() => js.run(file)) 25 | .catch(log.error) 26 | }) 27 | } 28 | 29 | 30 | function refresh(file) { 31 | log(file, 'changed - Refreshing') 32 | return chrome.send('Page.reload', { ignoreCache: true }) 33 | } 34 | 35 | function updateUrlInStyles(file) { 36 | return Promise.all(Array.from(css.styles.values()).map(style => { 37 | file = path.relative(path.dirname(style.path), file) 38 | 39 | const string = style.text.split(new RegExp(file + '(?:\\?[0-9]*)?', 'gi')).join(file + '?' + Date.now()) 40 | 41 | if (string !== style.text) { 42 | style.text = string 43 | return css.set(style) 44 | } 45 | })) 46 | } 47 | 48 | function updateSrc(file) { 49 | file = path.relative(config.serve, path.join(process.cwd(), file)) 50 | const src = '[src*="' + file + '"]' 51 | 52 | if (path.extname(file) === '.svg') { 53 | chrome.send('Runtime.evaluate', { 54 | expression: `document.querySelectorAll('use[href*="${ file }"]').forEach(el => { 55 | el.setAttribute('href', el.getAttribute('href').replace(/${file}/i, '${file}&wright=' + Date.now())) 56 | })` 57 | }) 58 | } 59 | 60 | return chrome.send('Runtime.evaluate', { 61 | expression: `document.querySelectorAll('img${ src },audio${ src },video${ src }').forEach(el => { 62 | el.src = '${file + '?' + Date.now()}' 63 | })` 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /lib/watch/css.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | , url = require('url') 5 | , path = require('path') 6 | , log = require('../log') 7 | , config = require('../config') 8 | , watch = require('./watch') 9 | , chrome = require('../chrome') 10 | , assets = require('./assets') 11 | , js = require('./js') 12 | 13 | const styles = new Map() 14 | 15 | module.exports = function() { 16 | chrome.on('CSS.styleSheetAdded', styleSheetParsed) 17 | } 18 | 19 | module.exports.styles = styles 20 | module.exports.set = setStyle 21 | 22 | function styleSheetParsed(style) { 23 | style = style.header 24 | 25 | if (style.isInline || !style.sourceURL || !style.sourceURL.startsWith(config.url)) 26 | return 27 | 28 | const pathname = style.sourceURL && decodeURIComponent(url.parse(style.sourceURL).pathname) 29 | , localPath = pathname && path.join(config.serve, pathname) 30 | , css = config.css.find(s => pathname === s.path || pathname === '/' + s.path) 31 | 32 | if (!style.ownerNode) 33 | return assets.watch(localPath) 34 | 35 | if (!css && path.extname(localPath) !== '.css') 36 | return 37 | 38 | style.path = localPath 39 | 40 | if (!css) { 41 | fs.readFile(localPath, 'utf8', (err, content) => { 42 | if (err) 43 | return 44 | 45 | style.text = content || '' 46 | styles.set(localPath, style) 47 | }) 48 | } 49 | 50 | watch(css ? css.watch : localPath, file => { 51 | const pathname = css ? css.path : path.relative(config.serve, file) 52 | , href = url.resolve(config.url, pathname) 53 | 54 | log.debug('Injecting', file) 55 | chrome.send('Runtime.evaluate', { 56 | expression: `document.querySelectorAll('link').forEach(el => { 57 | if (el.href.startsWith('${ href }')) 58 | el.href = '${href + '?' + Date.now()}' 59 | })` 60 | }) 61 | .then(() => js.run(pathname)) 62 | .catch(log.error) 63 | }, css) 64 | } 65 | 66 | function setStyle(style) { 67 | return chrome.send('CSS.setStyleSheetText', { 68 | styleSheetId: style.styleSheetId, 69 | text: style.text 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /lib/watch/index.js: -------------------------------------------------------------------------------- 1 | const log = require('../log') 2 | , config = require('../config') 3 | , watch = require('./watch') 4 | , js = require('./js') 5 | , css = require('./css') 6 | , chrome = require('../chrome') 7 | 8 | module.exports = function() { 9 | 10 | if (config.watch) 11 | watch(config.watch, refresh, true) 12 | 13 | if (!config.external) 14 | watch(config.main, refresh) 15 | 16 | return Promise.resolve() 17 | .then(js) 18 | .then(css) 19 | } 20 | 21 | function refresh(file) { 22 | log(file, 'changed - Refreshing') 23 | return chrome.send('Page.reload', { ignoreCache: true }) 24 | } 25 | -------------------------------------------------------------------------------- /lib/watch/js.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | , url = require('url') 5 | , path = require('path') 6 | , log = require('../log') 7 | , utils = require('../utils') 8 | , session = require('../session') 9 | , config = require('../config') 10 | , watch = require('./watch') 11 | , jail = require('../jail') 12 | , chrome = require('../chrome') 13 | 14 | let scripts = [] 15 | 16 | const srcCache = {} 17 | , lastInject = {} 18 | 19 | module.exports = function() { 20 | chrome.on('Debugger.scriptParsed', scriptParsed) 21 | scripts = session.get('scripts') || [] 22 | scripts.forEach(scriptParsed) 23 | } 24 | 25 | module.exports.run = run 26 | 27 | function scriptParsed(script) { 28 | if (!script.url || !script.url.startsWith(config.url) || scripts.url) 29 | return 30 | 31 | const pathname = script.url && decodeURIComponent(url.parse(script.url).pathname) 32 | , localPath = pathname && path.join(config.serve, pathname) 33 | , js = config.js.find(s => pathname === s.path || pathname === '/' + s.path) 34 | 35 | if ((!js && path.extname(localPath) !== '.js') || pathname === '/wright.js') 36 | return 37 | 38 | script.path = pathname 39 | 40 | watch(js ? js.watch : localPath, file => { 41 | if (!config.run) 42 | return refresh(file) 43 | 44 | utils.promisify(js 45 | ? js.compile 46 | : fn => fs.readFile(file, 'utf8', fn) 47 | ) 48 | .then(code => inject(script, code, file)) 49 | .catch(err => { 50 | log.error(err) 51 | }) 52 | }, js) 53 | } 54 | 55 | function inject(script, content, file) { 56 | log.debug(file, 'changed, compiling...', Date.now() - lastInject[script.scriptId] > config.watchThrottle) 57 | const src = content.code || content 58 | if (srcCache[script.scriptId] === src) { 59 | if (!config.watchThrottle || Date.now() - lastInject[script.scriptId] > config.watchThrottle) 60 | refresh(file) 61 | 62 | return 63 | } 64 | 65 | save(script) 66 | lastInject[script.scriptId] = Date.now() 67 | srcCache[script.scriptId] = src 68 | return chrome.send('Debugger.setScriptSource', { 69 | scriptId: script.scriptId, 70 | scriptSource: jail(src) 71 | }).then(result => { 72 | result.status === 'Ok' 73 | ? log.debug('Injected', script.path) 74 | : log.error('Error injecting', file, result) 75 | 76 | return run(file) 77 | }) 78 | } 79 | 80 | function save(script) { 81 | if (scripts.some(s => s.scriptId === script.scriptId)) 82 | return 83 | 84 | scripts.push(script) 85 | session.set('scripts', scripts) 86 | } 87 | 88 | function run(filename) { 89 | if (!config.run || config.run === true) 90 | return 91 | 92 | require('../serve').ubre.publish('run', { method: config.run, arg: { path: filename } }) // eslint-disable-line 93 | } 94 | 95 | function refresh(file) { 96 | log(file, 'changed - Refreshing') 97 | return chrome.send('Page.reload', { ignoreCache: true }).catch(log.error) 98 | } 99 | -------------------------------------------------------------------------------- /lib/watch/watch.js: -------------------------------------------------------------------------------- 1 | const chokidar = require('chokidar') 2 | , fs = require('fs') 3 | , path = require('path') 4 | , log = require('../log') 5 | , config = require('../config') 6 | 7 | const watching = new Map() 8 | 9 | module.exports = function watch(file, changed, glob) { 10 | if (watching.has(file)) { 11 | watching.get(file).close() 12 | } else { 13 | log.debug('Watching', glob 14 | ? file 15 | : path.relative(config.serve, file), 16 | fs.existsSync(file) || glob ? '' : '\x1b[31m(not found on disk)\x1b[0m' 17 | ) 18 | } 19 | 20 | function normalizePath(file) { 21 | require('../serve').ubre.publish('reload') // eslint-disable-line 22 | const filePath = path.isAbsolute(file) ? path.relative(process.cwd(), file) : file 23 | log.debug('Changed', filePath) 24 | changed && changed(filePath) 25 | } 26 | 27 | watching.set(file, chokidar.watch(file, { 28 | ignoreInitial: true, useFsEvents: false 29 | }).on('add', normalizePath).on('change', normalizePath)) 30 | } 31 | 32 | module.exports.watching = watching 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wright", 3 | "version": "2.2.1", 4 | "description": "", 5 | "author": "Rasmus Porsager ", 6 | "license": "WTFPL", 7 | "main": "lib/index.js", 8 | "bin": { 9 | "wright": "./bin/wright" 10 | }, 11 | "preferGlobal": true, 12 | "scripts": { 13 | "build": "rollup -c", 14 | "prepublishOnly": "npm run build", 15 | "try:rollup": "npm install && cd examples/rollup && npm install && node wright", 16 | "try:mithril": "npm install && cd examples/mithril && npm install && npm run wright", 17 | "try:simple": "npm install && cd examples/simple && npm run wright", 18 | "test": "tape test/**" 19 | }, 20 | "repository": "porsager/wright", 21 | "dependencies": { 22 | "chokidar": "3.5.3", 23 | "dotenv": "16.0.0", 24 | "ey": "0.9.1", 25 | "finalhandler": "1.2.0", 26 | "http-proxy": "1.18.1", 27 | "inquirer": "8.2.2", 28 | "minimist": "1.2.6", 29 | "pws": "5.0.2", 30 | "serve-static": "1.15.0", 31 | "source-map": "0.7.3", 32 | "ubre": "0.0.23", 33 | "ws": "7.5.7" 34 | }, 35 | "devDependencies": { 36 | "bss": "1.6.4", 37 | "mithril": "2.0.4", 38 | "rollup": "2.36.1", 39 | "rollup-plugin-buble": "0.19.8", 40 | "rollup-plugin-commonjs": "10.1.0", 41 | "rollup-plugin-node-resolve": "5.2.0", 42 | "tape": "5.1.1" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import buble from 'rollup-plugin-buble' 2 | import nodeResolve from 'rollup-plugin-node-resolve' 3 | import commonjs from 'rollup-plugin-commonjs' 4 | 5 | export default { 6 | input: 'lib/browser/index.js', 7 | plugins: [ 8 | nodeResolve({ 9 | browser: true 10 | }), 11 | commonjs(), 12 | buble({ 13 | transforms: { 14 | dangerousTaggedTemplateString: true 15 | } 16 | }) 17 | ], 18 | output: { 19 | file: 'lib/browser/wright.js', 20 | format: 'iife', 21 | sourcemap: true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/data-dir.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const { wrightDataDirectory, getAppDataDirectory } = require('../lib/utils') 3 | 4 | const fakeProcess = { 5 | linux() { 6 | return { 7 | platform: 'linux', 8 | env: { 9 | HOME: '/home/porsager', 10 | PATH: [ 11 | '/home/porsager/something/something', 12 | '/usr/bin/something' 13 | ] 14 | .join(':') 15 | } 16 | } 17 | }, 18 | windows() { 19 | return { 20 | platform: 'win32', 21 | env: { 22 | HOMEPATH: 'C:\\Users\\porsager', 23 | PATH: [ 24 | 'C:\\Users\\porsager\\something\\something', 25 | 'C:\\Program Files (x86)\\something', 26 | 'C:\\Program Files\\something', 27 | ] 28 | .join(';') 29 | } 30 | } 31 | }, 32 | wsl() { 33 | return { 34 | platform: 'linux', 35 | env: { 36 | HOME: '/home/porsager', 37 | WSL_DISTRO_NAME: 'Ubuntu-18.04', 38 | PATH: [ 39 | '/home/porsager/something/something', 40 | '/usr/bin/something', 41 | '/mnt/c/Users/porsager/example' 42 | ] 43 | .join(':') 44 | } 45 | } 46 | } 47 | } 48 | 49 | // So the test's pass on all platforms (path.join, path.resolve etc) 50 | const normalize = s => s.replace(/\\/g, '/') 51 | 52 | test('linux', (t) => { 53 | const process = fakeProcess.linux() 54 | const wrightData = wrightDataDirectory(process) 55 | const appData = normalize(getAppDataDirectory(process, 'example')) 56 | 57 | t.equals(normalize(wrightData.chrome), '/home/porsager/.wright', 'Chrome') 58 | t.equals(appData, '/home/porsager/.wright/example', 'AppData') 59 | t.equals(normalize(wrightData.node), '/home/porsager/.wright', 'Node') 60 | t.end() 61 | }) 62 | 63 | test('windows', (t) => { 64 | const process = fakeProcess.windows() 65 | const wrightData = wrightDataDirectory(process) 66 | const appData = normalize(getAppDataDirectory(process, 'example')) 67 | 68 | t.equals(normalize(wrightData.chrome), 'C:/Users/porsager/.wright', 'Chrome') 69 | t.equals(appData, 'C:/Users/porsager/.wright/example', 'AppData') 70 | t.equals(normalize(wrightData.node), 'C:/Users/porsager/.wright', 'Node') 71 | t.end() 72 | }) 73 | 74 | test('wsl', (t) => { 75 | const process = fakeProcess.wsl() 76 | const wrightData = wrightDataDirectory(process) 77 | const appData = normalize(getAppDataDirectory(process, 'example')) 78 | 79 | t.equals(normalize(wrightData.chrome), 'C:/Users/porsager/.wright', 'Chrome') 80 | t.equals(appData, 'C:/Users/porsager/.wright/example', 'AppData') 81 | t.equals(normalize(wrightData.node), '/mnt/c/Users/porsager/.wright', 'Node') 82 | t.end() 83 | }) 84 | --------------------------------------------------------------------------------