├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── browserify-loader.js ├── browserify-loader.min.js ├── example ├── bar.js ├── data.json ├── dog.6.js ├── foo.js ├── helloMessage.jsx ├── index.html ├── main.jsx ├── math.6.js └── temp.js ├── package.json └── src ├── log.js ├── main.js └── module.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | bower_components 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Zhi Cun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | browserify-loader 2 | ================= 3 | 4 | A CommonJS Loader for browserify workflow [ES6 support]. 5 | 6 | 7 | ## What is browserify-loader 8 | 9 | `browserify-loader` is another CommonJS loader for browserify workflow. With BL, You don’t need any tools like watchify, browserify-middleware to auto build and serve bundle *js in development env. 10 | 11 | `browserify-loader` is similar with [requirejs](http://requirejs.org/), but: 12 | 13 | - follow [Modules/1.1.1](http://wiki.commonjs.org/wiki/Modules/1.1.1) like [Node](http://nodejs.org/) 14 | - get rid of wrapper code like `define()` 15 | - be compatible all `npm` package and all `bower` components which support `CommonJS`. like `underscore`, `backbone`, `jQuery` and so on. 16 | 17 | ## Getting start 18 | 19 | ### install 20 | 21 | Download `browserify-loader` with `npm` or `bower`: 22 | 23 | ```bash 24 | $ npm install browserify-loader 25 | ``` 26 | 27 | Put `browserify-loader.js` in your page: 28 | 29 | ```html 30 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 39 | 40 | ``` 41 | 42 | Then, `browserify-loader` will start to run for `main` file in your `package.json` file. 43 | 44 | ### options 45 | 46 | `browserify-loader` has two options to specify the `main` script or `package` location. and browserify-loader supports `coffee-script`. 47 | 48 | ```javascript 49 | 55 | ``` 56 | 57 | - **main**: the main entrance script like `app.js` in `node app.js` 58 | - **package**: the location where `browserify-loader` to load `package.json`, then get the main entrance from `main` property. 59 | - **extensions**: the enable extensions you want basing on your source code. `browserify-loader` now supports `.js`,`.6.js`(ES6), `json` and `jsx`(for react fans). 60 | 61 | > **main** 's priority is higher the **package** 's. 62 | 63 | ## example 64 | 65 | Look into [todomvc-in-bl](https://github.com/island205/todomvc-in-bl) , which is a demo project based on [todomvc](https://github.com/tastejs/todomvc) to show how to use `browserify-loader`. 66 | 67 | ## API 68 | 69 | ### define 70 | 71 | > The internal wrapper API. 72 | 73 | ### define.registerExtension 74 | 75 | Register extension to `browserify-loader`, like: 76 | 77 | ``` 78 | var to5Transform = require('6to5/lib/6to5/transformation/transform') 79 | 80 | define.registerExtension('jsx', function(script) { 81 | return to5Transform(script, {modules: "common"}).code 82 | }) 83 | ``` 84 | 85 | ### define.performance 86 | 87 | `browserify-loader`'s performance is important, and it is not ideal now yet! 88 | 89 | browserify-loader provide a method to get its performance: `define.performance()` 90 | 91 | Just think if there is no browserify-loader, where performance cost come from: 92 | 93 | - script load time 94 | 95 | and then thinking cost in browserify-loader: 96 | 97 | - xhr loading time, roughly equals script load time 98 | 99 | - define time, concat code, insert script tag and so on 100 | 101 | - analysis module's dependences 102 | 103 | - resolve dependences' uri, include get package.json recursively 104 | 105 | ### Update 106 | 107 | #### 0.5.2 108 | 109 | - hotfix 110 | 111 | #### 0.5.1 112 | 113 | - rewrite in es6 114 | 115 | #### 0.5.0 116 | 117 | - support ES6! 118 | - remove support `coffee-script` 119 | 120 | #### 0.4.2 121 | 122 | - improve for friendly debuging. 123 | 124 | #### 0.4.0 125 | 126 | - add `registerExtension` API 127 | - support `jsx` and `json` 128 | 129 | #### 0.3.0 130 | 131 | - use ES6's [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) instead of rsvp and eventemitter 132 | 133 | #### 0.2.0 134 | 135 | - support `coffee-script` 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "browserify-loader", 3 | "main": "browserify-loader.js", 4 | "version": "0.5.2", 5 | "homepage": "https://github.com/island205/browserify-loader", 6 | "authors": [ 7 | "island205 " 8 | ], 9 | "description": "CommonJS loader for browserify workflow", 10 | "keywords": [ 11 | "CommonJS", 12 | "browserify", 13 | "node.js" 14 | ], 15 | "license": "MIT", 16 | "ignore": [ 17 | "**/.*", 18 | "node_modules", 19 | "bower_components", 20 | "test", 21 | "tests" 22 | ], 23 | "devDependencies": { 24 | "react": "~0.12.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example/bar.js: -------------------------------------------------------------------------------- 1 | var foo = require('./foo.js') 2 | var xhr = require('xhr') 3 | var data = require('./data') 4 | console.log(data) 5 | require('./main') 6 | exports.bar = function () { 7 | foo.foo() 8 | } -------------------------------------------------------------------------------- /example/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "browserify-loader" 3 | } -------------------------------------------------------------------------------- /example/dog.6.js: -------------------------------------------------------------------------------- 1 | class Dog { 2 | constructor() { 3 | } 4 | update() { 5 | } 6 | } 7 | 8 | import * as math from "./math"; 9 | alert("2π = " + math.sum(math.pi, math.pi)); 10 | 11 | module.exports = Dog -------------------------------------------------------------------------------- /example/foo.js: -------------------------------------------------------------------------------- 1 | var xhr = require('xhr') 2 | var Dog = require('./dog') 3 | 4 | exports.foo = function () { 5 | console.log('foo') 6 | } -------------------------------------------------------------------------------- /example/helloMessage.jsx: -------------------------------------------------------------------------------- 1 | var React = require('../bower_components/react/react') 2 | 3 | var HelloMessage = React.createClass({ 4 | render: function() { 5 | return
Hello {this.props.name}
; 6 | } 7 | }); 8 | 9 | module.exports = HelloMessage -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Browserify Loader 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Browser Loader 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /example/main.jsx: -------------------------------------------------------------------------------- 1 | var HelloMessage = require('./HelloMessage') 2 | var React = require('../bower_components/react/react') 3 | var mountNode = document.getElementById('mount') 4 | React.render(, mountNode) -------------------------------------------------------------------------------- /example/math.6.js: -------------------------------------------------------------------------------- 1 | export function sum(x, y) { 2 | return x + y; 3 | } 4 | export var pi = 3.141593; -------------------------------------------------------------------------------- /example/temp.js: -------------------------------------------------------------------------------- 1 | var temp = 'test'; 2 | console.log(temp) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "browserify-loader", 3 | "version": "0.5.2", 4 | "description": "Another CommonJS Loader[ES6 support]", 5 | "main": "example/bar.js", 6 | "scripts": { 7 | "test": "node test", 8 | "build": "browserify -t 6to5ify src/main.js > browserify-loader.js && uglifyjs browserify-loader.js -o browserify-loader.min.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/island205/browserify-loader.git" 13 | }, 14 | "keywords": [ 15 | "commonjs", 16 | "module", 17 | "loader", 18 | "browserify" 19 | ], 20 | "author": "island205@gmail.com", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/island205/browserify-loader/issues" 24 | }, 25 | "homepage": "https://github.com/island205/browserify-loader", 26 | "devDependencies": { 27 | "6to5": "^2.12.6", 28 | "6to5ify": "^3.1.2", 29 | "coffee-script": "^1.7.1", 30 | "react": "^0.12.1", 31 | "react-tools": "^0.12.1", 32 | "xhr": "^2.0.1", 33 | "searequire": "^1.5.3" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/log.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var debug = false 4 | module.exports = function () { 5 | debug && console.log.apply(console, arguments) 6 | } -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var xhr = require('xhr') 4 | var Module = require('./module') 5 | var url = require('url') 6 | var to5Transform = require('6to5/lib/6to5/transformation/transform') 7 | 8 | Module.registerExtension('js', (script) => script) 9 | 10 | Module.registerExtension('6.js', function(script) { 11 | return to5Transform(script, {modules: "common", blacklist: ["react"]}).code 12 | }) 13 | 14 | Module.registerExtension('json', (script) => `module.exports = ${script}`) 15 | 16 | Module.registerExtension('jsx', function(script) { 17 | return to5Transform(script, {modules: "common"}).code 18 | }) 19 | 20 | define = window.define = Module.define 21 | define.performance = Module.performance 22 | define.registerExtension = Module.registerExtension 23 | 24 | function loadMainModule(mainScriptUri) { 25 | var mainModule = new Module(mainScriptUri) 26 | mainModule.load().then(function() { 27 | mainModule.compile() 28 | performance.mark('bootstrap_end') 29 | },function(err) { 30 | throw err 31 | }).catch(function(err){ 32 | console.error(err.stack) 33 | }) 34 | } 35 | 36 | function bootstrap() { 37 | performance.mark('bootstrap_start') 38 | var blScript = document.getElementById('bl-script') 39 | var packagePath 40 | var mainScriptPath 41 | var extensions = [] 42 | if (blScript) { 43 | mainScriptPath = blScript.getAttribute('main') 44 | packagePath = blScript.getAttribute('package') || './' 45 | extensions = blScript.getAttribute('extensions') 46 | if (extensions) { 47 | extensions = extensions.split(' ') 48 | } 49 | } else { 50 | packagePath = './' 51 | } 52 | extensions.unshift('js') 53 | Module.extensions = extensions 54 | if (mainScriptPath) { 55 | mainScriptPath = url.resolve(location.origin, mainScriptPath) 56 | loadMainModule(mainScriptPath) 57 | } else { 58 | packagePath = url.resolve(url.resolve(location.origin, packagePath), './package.json') 59 | xhr({ 60 | uri: packagePath, 61 | headers: { 62 | "Content-Type": "application/json" 63 | } 64 | }, function(err, resp, body) { 65 | if (err) { 66 | throw new Error('canot get main module') 67 | } 68 | var pkg = JSON.parse(body) 69 | mainScriptPath = pkg.browser || pkg.main || 'index.js' 70 | mainScriptPath = url.resolve(packagePath, mainScriptPath) 71 | loadMainModule(mainScriptPath) 72 | }) 73 | } 74 | } 75 | 76 | bootstrap() 77 | -------------------------------------------------------------------------------- /src/module.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var xhr = require('xhr') 4 | var parseDependencies = require('searequire') 5 | var url = require('url') 6 | var log = require('./log') 7 | 8 | function getPackageMainModuleUri(searchPath, dep, callback) { 9 | var childModule = null 10 | var uri = '' 11 | var pkgUri = url.resolve(searchPath, './') 12 | var oldSearchPath = searchPath 13 | var originDep = dep 14 | // global/window 15 | dep = dep.split('/') 16 | if (dep.length > 1) { 17 | childModule = dep 18 | dep = childModule.shift() 19 | childModule = childModule.join('/') 20 | } else { 21 | dep = dep.join('/') 22 | } 23 | pkgUri = `${pkgUri}node_modules/${dep}/package.json` 24 | xhr({ 25 | uri: pkgUri, 26 | headers: { 27 | "Content-Type": "application/json" 28 | } 29 | }, function(err, resp, body) { 30 | if (err) { 31 | searchPath = url.resolve(searchPath, '../') 32 | if (oldSearchPath != searchPath) { 33 | getPackageMainModuleUri(searchPath, originDep, callback) 34 | } else { 35 | callback(`pkg: ${originDep} not Found`) 36 | } 37 | return 38 | } 39 | try { 40 | var pkg = JSON.parse(body) 41 | if (childModule) { 42 | uri = childModule 43 | } else { 44 | uri = pkg.browser || pkg.main || 'index.js' 45 | } 46 | uri = `./node_modules/${dep}/${uri}` 47 | uri = url.resolve(searchPath, uri) 48 | callback(null, uri) 49 | } catch (err) { 50 | callback(err) 51 | } 52 | }) 53 | } 54 | 55 | class Module { 56 | constructor(uri) { 57 | this.uri = uri 58 | this.uris = {} 59 | this.status = Module.STATUS.CREATED 60 | Module.modules[uri] = this 61 | } 62 | static get(uri) { 63 | var module = this.modules[uri] 64 | var ext 65 | if (!module) { 66 | for (var i = 0; i < Module.extensions.length; i++) { 67 | ext = Module.extensions[i] 68 | module = this.modules[`${uri}.${ext}`] 69 | if (module) { 70 | break 71 | } 72 | } 73 | } 74 | if (!module) { 75 | module = this.modules[uri] = new Module(uri) 76 | } 77 | return module 78 | } 79 | 80 | static define(uri, factory) { 81 | var module = Module.modules[uri] 82 | module.factory = factory 83 | module.status = Module.STATUS.DEFINED 84 | module.loadDeps() 85 | } 86 | 87 | static registerExtension(name, compile) { 88 | Module._extensions[name] = compile 89 | } 90 | 91 | static resolve(uri) { 92 | 93 | log(`loaded ${uri}`) 94 | 95 | var loadPromise = Module.loadPromises[uri] 96 | if (loadPromise) { 97 | loadPromise.resolve() 98 | } else { 99 | throw `can't find loadPromise for ${uri}` 100 | } 101 | } 102 | 103 | static reject(uri, err) { 104 | 105 | log(`reject load ${uri}`, err) 106 | 107 | var loadPromise = Module.loadPromises[uri] 108 | if (loadPromise) { 109 | loadPromise.reject(err) 110 | } else { 111 | throw `can't find loadPromise for ${uri}` 112 | } 113 | } 114 | 115 | static performance() { 116 | var uri, module 117 | var allCost 118 | var normalCost = 0 119 | var compileCost, loadCost 120 | for (uri in Module.modules) { 121 | if (Module.modules.hasOwnProperty(uri)) { 122 | 123 | performance.measure(`${uri}_compile`, `${uri}_compile_start`, `${uri}_compile_end`) 124 | performance.measure(`${uri}_load`, `${uri}_load_start`, `${uri}_load_end`) 125 | compileCost = performance.getEntriesByName(`${uri}_compile`)[0].duration 126 | loadCost = performance.getEntriesByName(`${uri}_load`)[0].duration 127 | 128 | normalCost += compileCost + loadCost 129 | } 130 | } 131 | 132 | performance.measure('all_cost', 'bootstrap_start', 'bootstrap_end'); 133 | 134 | allCost = performance.getEntriesByName('all_cost')[0].duration 135 | 136 | console.log('performance:', allCost / normalCost * 6) 137 | } 138 | 139 | resolve(dep) { 140 | var uri = '' 141 | var promise = new Promise((resolve, reject) => { 142 | if (/^\./.test(dep)) { 143 | uri = url.resolve(this.uri, dep) 144 | this.uris[dep] = uri 145 | resolve(uri) 146 | } else { 147 | getPackageMainModuleUri(this.uri, dep, (err, uri) => { 148 | if (err) { 149 | reject(err) 150 | } else { 151 | this.uris[dep] = uri 152 | resolve(uri) 153 | } 154 | }) 155 | } 156 | }) 157 | return promise 158 | } 159 | 160 | compile() { 161 | var module = {} 162 | var exports = module.exports = {} 163 | var require = (dep) => { 164 | var module = Module.get(this.uris[dep]) 165 | return module.exports || module.compile() 166 | } 167 | 168 | performance.mark(`${this.uri}_compile_start`) 169 | 170 | log(`compile ${this.uri}`) 171 | 172 | this.factory(require, exports, module) 173 | 174 | performance.mark(`${this.uri}_compile_end`) 175 | 176 | return this.exports = module.exports 177 | } 178 | 179 | load() { 180 | this.status = Module.STATUS.LOADING 181 | if (Module.loadPromises[this.uri] && Module.loadPromises[this.uri].promise) { 182 | return Module.loadPromises[this.uri].promise 183 | } 184 | Module.loadPromises[this.uri] = {} 185 | var loadPromise = Module.loadPromises[this.uri].promise = new Promise((resolve, reject) => { 186 | 187 | log(`load ${this.uri}`) 188 | 189 | Module.loadPromises[this.uri].resolve = resolve 190 | Module.loadPromises[this.uri].reject = reject 191 | this.loadScript() 192 | .then(() => this.defineScript()) 193 | .catch((err) => reject(err)) 194 | }) 195 | return loadPromise 196 | } 197 | 198 | loadScript() { 199 | 200 | performance.mark(`${this.uri}_load_start`) 201 | 202 | var uri = this.uri 203 | var ext = uri.split('.').pop() 204 | var extIndex = 0 205 | 206 | function tryExt(uri, callback) { 207 | xhr({ 208 | uri: uri + '.' + Module.extensions[extIndex], 209 | headers: { 210 | "Content-Type": "text/plain" 211 | } 212 | }, (err, resp, body) => { 213 | if (err) { 214 | if (extIndex >= Module.extensions.length - 1) { 215 | callback(new Error(`cannot GET ${uri}`)) 216 | } else { 217 | extIndex++ 218 | tryExt(uri, callback) 219 | } 220 | } else { 221 | callback(err, resp, body) 222 | } 223 | }) 224 | } 225 | 226 | return new Promise((resolve, reject) => { 227 | if (ext == uri || Module.extensions.indexOf(ext) == -1) { // no ext 228 | tryExt(uri, (err, resp, body) => { 229 | performance.mark(`${this.uri}_load_end`) 230 | if (err) { 231 | reject(err) 232 | } else { 233 | this.ext = Module.extensions[extIndex] 234 | this.script = body 235 | resolve() 236 | } 237 | }) 238 | } else { // has ext 239 | this.ext = ext 240 | xhr({ 241 | uri: uri, 242 | headers: { 243 | "Content-Type": "text/plain" 244 | } 245 | }, (err, resp, body) => { 246 | 247 | performance.mark(`${this.uri}_load_end`) 248 | 249 | if (err) { 250 | reject(err) 251 | } else { 252 | this.script = body 253 | resolve() 254 | } 255 | }) 256 | } 257 | }) 258 | } 259 | 260 | defineScript() { 261 | try { 262 | this.script = Module._extensions[this.ext](this.script) 263 | } catch (err) { 264 | Module.reject(this.uri, err) 265 | } 266 | 267 | var code = this.script 268 | .split('\n') 269 | .map(line => ` ${line}`) 270 | .join('\n') 271 | 272 | var sourceURL = 273 | this.uri.split('.').pop() != this.ext 274 | ? `${this.uri}.${this.ext}` 275 | : this.uri 276 | 277 | code = 278 | `define("${this.uri}", function(require, exports, module) {\n${code}\n})\n//# sourceURL=${sourceURL}` 279 | 280 | var scriptNode = document.createElement('script') 281 | scriptNode.innerHTML = code 282 | scriptNode.type = 'text/javascript' 283 | document.body.appendChild(scriptNode) 284 | } 285 | 286 | loadDeps() { 287 | this.getDeps() 288 | var depModules = [] 289 | var module 290 | var resolveDepPromises = this.deps.map((dep) => { 291 | return this.resolve(dep) 292 | }) 293 | Promise.all(resolveDepPromises).then((deps) => { 294 | this.deps = deps 295 | var loadDepPromises = this.deps.map(function(uri) { 296 | var module = Module.get(uri) 297 | return module.load() 298 | }) 299 | Promise.all(loadDepPromises).then(() => { 300 | Module.resolve(this.uri) 301 | }).catch((err) => { 302 | Module.reject(this.uri, err) 303 | }) 304 | }).catch((err) => { 305 | Module.reject(this.uri, err) 306 | }) 307 | } 308 | 309 | getDeps() { 310 | var deps = parseDependencies(this.script) 311 | this.deps = deps.map(dep => dep.path) 312 | } 313 | } 314 | 315 | Module.STATUS = { 316 | CREATED: 0, 317 | LOADING: 1, 318 | DEFINED: 2, 319 | LOADED: 3 320 | } 321 | Module.modules = {} 322 | Module._extensions = {} 323 | Module.loadPromises = {} 324 | 325 | module.exports = Module 326 | --------------------------------------------------------------------------------