UNPKG

9.23 kBMarkdownView Raw
1<img src=https://reun.solsort.com/icon.png width=96 height=96 align=right>
2
3[![website](https://img.shields.io/badge/website-reun.solsort.com-blue.svg)](https://reun.solsort.com/)
4[![github](https://img.shields.io/badge/github-solsort/reun-blue.svg)](https://github.com/solsort/reun)
5[![travis](https://img.shields.io/travis/solsort/reun.svg)](https://travis-ci.org/solsort/reun)
6[![npm](https://img.shields.io/npm/v/reun.svg)](https://www.npmjs.com/package/reun)
7
8TODO: unit testing
9TODO: documentaiton, - merge into source
10
11# REUN - require(unpkg)
12
13Reun is:
14
15- 100% client-side nodejs-like `require` for the browser.
16- using https://unpkg.com/.
17- dynamic, just `require(...)` whatever module you want from your source file. No need for `package.json`, - versions can be passed to require, i.e. `require('module@1.2.3')`.
18- pretending to be a synchronous, even though it is asynchrounous. Tries to work in the typical cases, and will always fail in certain documented edge cases. Pragmatic, and not standard compliant.
19- able to directly load many nodejs modules, that has not been packaged for the browser.
20- adding custom functionality when desired, i.e. `module.meta`
21
22## API
23
24- `reun.eval(code, [opt])` execute `code`, where `code` is either a function, or the string source of a module. `require()` is available and is pretending to be synchronous, and done relative to the `opt.uri`. Returns a promise of the function result or module-exports.
25- `reun.require(module, [opt])` loads a module, path is relative to the `location.href` if available. Returns a promise.
26
27## Usage example
28
29`index.html`:
30```html
31<!DOCTYPE html>
32<html>
33 <body>
34 <script src=https://unpkg.com/reun></script>
35 <script>reun.require('./example.js');</script>
36 </body>
37</html>
38```
39
40`example.js`:
41```javascript
42var uniq = require('uniq');
43console.log(uniq([1,4,2,8,4,2,1,3,2]));
44```
45
46## Extensions
47
48- `require('module@0.2.3')` allows you to require a specific version
49- `module.meta` allows you to set meta information about your module, - this may later be used to automatically package the module for npm, cordova, ...
50
51## Incompatibilities
52
53The implementation is a hack. We want to _pretend_ to be synchronous, but we also do not want to block the main thread. Instead `require` throws an exception when a module is not loaded yet. When we run a file, we catch this exception, load the module asynchrounously, and then rerun the file. Later on we might also search the source for `require("...")`, or `require('...')` and try to preload these modules, but this is not implemented yet.
54
55Also we just resolve the module name as `'https://unpkg.com/' + module_name`. To be more compatible with node modules, we may check the `package.json` in the future to make sure that the relative paths in the require works.
56
57- Custom exceptions from `require` should not caught.
58- Code before a require, may be executed multiple times, - should be side-effect free.
59- `require` may fail within callbacks, if the module has not been loaded before.
60- If the source lives in a subdirectory, and the module is not packaged for the web, and contains relative paths, - the paths are wrongly resolved. A workaround is to `require('module/lib/index.js')` instead of `require('module')`.
61- It does obviously not work with every module.
62
63In spite of these limitations, it is still possible to `require` many nodejs module directly to the web.
64
65
66## Project setup
67
68 (function() { 'use strict';
69 var da = typeof direape !== 'undefined' ? direape : require('direape');
70 da.testSuite('reun');
71 var reun = self.reun || {};
72 var modules = {
73 reun: reun,
74 direape: da
75 };
76
77## `reun.eval(src|fn, opt);`
78
79Functions will be called as a module with `require`, `exports`, and `module` as parameters, - similar to <http://requirejs.org/docs/commonjs.html>
80
81 var runQueue = new Promise((resolve) => da.ready(() => resolve()));
82
83 reun.eval = (fn, opt) => {
84 runQueue = runQueue.then(() => do_eval(fn, opt))
85 .catch((e) => da.nextTick(() => { throw e; }));
86 return runQueue;
87 };
88
89 da.handle('reun:eval', (fn, opt) =>
90 reun.eval(fn, opt).then(da.jsonify));
91
92## `reun.require(module-name, opt);`
93
94 reun.require = (name, opt) =>
95 reun.eval('module.exports = require("' + name + '",' +
96 JSON.stringify(opt || {}) + ');',
97 Object.assign({uri: self.location && self.location.href || './'}, opt));
98
99 da.handle('reun:require', reun.require);
100
101## Implementation details
102
103### moduleUrl
104
105Convert a require-address to a url.
106path is baseurl used for mapping relative file paths (`./hello.js`) to url.
107
108 function moduleUrl(module, opt) {
109 var path = opt.uri || '';
110
111 if(module.slice(0,4) === 'reun') {
112 return 'reun';
113 }
114
115 if(module.startsWith('https:') ||
116 module.startsWith('http:')) {
117 return module;
118 }
119 path = path.replace(/[?#].*/, '');
120 path = (module.startsWith('.')
121 ? path.replace(/[/][^/]*$/, '/')
122 : 'https://unpkg.com/');
123 path = path + module;
124 while(path.indexOf('/./') !== -1) {
125 path = path.replace('/./', '/');
126 }
127 var prevPath;
128 do {
129 prevPath = path;
130 path = path.replace(/[/][^/]*[/][.][.][/]/g, '/');
131 } while(path !== prevPath);
132 return path;
133 }
134
135### do_eval
136
137 function do_eval(fn, opt) {
138 opt = opt || {};
139 if(typeof fn === 'string') {
140 fn = stringToFunction(fn, opt);
141 }
142 return executeModule(fn, opt);
143 }
144
145### executeModule
146
147 function executeModule(fn, opt) {
148 opt.uri = opt.uri || '';
149 var require = (name, opt) => reun_require(name, opt, module);
150 var module = {
151 require: require,
152 uri: opt.uri,
153 id: opt.uri.replace('https://unpkg.com/', '').replace(/@[^/]*/, ''),
154 exports: {}
155 };
156 if(opt.main) {
157 require.main = module;
158 }
159
160 return rerunModule(fn, module);
161 }
162
163### rerunModule
164
165 function rerunModule(fn, module) {
166 var result;
167 try {
168 fn(module.require, module.exports, module);
169 result = module.exports;
170 } catch (e) {
171 if(e.constructor !== RequireError) {
172 throw e;
173 }
174 return da.call(da.nid, 'da:GET', e.url)
175 .catch(() => {
176 throw new Error('require could not load "' + e.url + '" ' +
177 'Possibly module incompatible with http://reun.solsort.com/'); })
178 .then((moduleSrc) => executeModule(stringToFunction(moduleSrc, e.opt),
179 e.opt))
180 .then((exports) => assignModule(e.url, exports))
181 .then(() => rerunModule(fn, module));
182 }
183 return Promise.resolve(result);
184 }
185
186### `shortName(uri)`
187
188 function assignModule(uri, exports) {
189
190 modules[uri] = exports;
191
192Find the short name of the module, and remember it by that alias,
193to make sure that later requires for the module without version/url
194returns the already loaded module.
195
196
197 if(exports.meta && exports.meta.id) {
198 modules[exports.meta.id] = exports;
199 }
200
201 var name = uri
202 .replace('https://unpkg.com/', '')
203 .replace(/[@/].*/, '');
204 if(!modules[name]) {
205 modules[name] = exports;
206 }
207 }
208
209### reun_require
210
211 function reun_require(name, opt, parentModule) {
212 if(modules[name]) {
213 return modules[name];
214 }
215 var url = moduleUrl(name, parentModule);
216 if(!modules[url]) {
217 throw new RequireError(name, url, opt);
218 }
219 return modules[url];
220 }
221
222### stringToFunction
223
224 function stringToFunction(src, opt) {
225 var wrappedSrc = '(function(require,exports,module){' +
226 src + '})//# sourceURL=' + opt.uri;
227 return eval(wrappedSrc);
228 }
229
230### RequireError
231
232When trying to load at module, that is not loaded yet, we throw this error:
233
234 function RequireError(module, url, opt) {
235 this.module = module;
236 this.url = url;
237 opt = opt || {};
238 opt.uri = url;
239 this.opt = opt;
240 }
241 RequireError.prototype.toString = function() {
242 return 'RequireError:' + this.module +
243 ' url:' + this.url;
244 };
245
246## Main / test runner
247
248 da.ready(() => {
249 if((da.isNodeJs() && require.main === module && process.argv[2] === 'test') ||
250 (self.REUN_RUN_TESTS)) {
251 da.runTests('reun')
252 .then(() => da.isNodeJs() && process.exit(0))
253 .catch(() => da.isNodeJs() && process.exit(1));
254 }
255 });
256 if(typeof module === 'object') {
257 module.exports = reun;
258 } else {
259 self.reun = reun;
260 }
261
262## end
263 })();
264
265# License
266
267This software is copyrighted solsort.com ApS, and available under GPLv3, as well as proprietary license upon request.
268
269Versions older than 10 years also fall into the public domain.
270
271