├── .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 |
--------------------------------------------------------------------------------