UNPKG

8.76 kBJavaScriptView Raw
1'use strict';
2
3var fs = require('fs');
4var path = require('path');
5var assert = require('assert');
6var Transform = require('stream').Transform;
7var Parser = require('jade/lib/parser.js');
8var jade = require('jade/lib/runtime.js');
9var React = require('react');
10var staticModule = require('static-module');
11var resolve = require('resolve');
12var uglify = require('uglify-js');
13var acornTransform = require('./lib/acorn-transform.js');
14var Compiler = require('./lib/compiler.js');
15var JavaScriptCompressor = require('./lib/java-script-compressor.js');
16
17var reactRuntimePath = require.resolve('react');
18
19function isTemplateLiteral(str) {
20 return str && typeof str === 'object' &&
21 str.raw && typeof str.raw === 'object' &&
22 str.raw.length === 1 && typeof str.raw[0] === 'string';
23}
24
25exports = (module.exports = browserifySupport);
26function browserifySupport(options, extra) {
27 if (isTemplateLiteral(options)) {
28 return compile(options.raw[0]);
29 }
30 function transform(filename) {
31 function clientRequire(path) {
32 return require(clientRequire.resolve(path));
33 }
34 clientRequire.resolve = function (path) {
35 return resolve.sync(path, {
36 basedir: path.dirname(filename)
37 });
38 };
39 var src = '';
40 var stream = new Transform();
41 stream._transform = function (chunk, encoding, callback) {
42 src += chunk;
43 callback();
44 };
45 stream._flush = function (callback) {
46 src = acornTransform(src, {
47 TaggedTemplateExpression: function (node) {
48 var quasi = '(function () {' +
49 'var quasi = ' + acornTransform.stringify(node.quasi.quasis.map(function (q) {
50 return q.value.cooked;
51 })) + ';' +
52 'quasi.raw = ' + acornTransform.stringify(node.quasi.quasis.map(function (q) {
53 return q.value.raw;
54 })) + ';' +
55 'return quasi;}())';
56
57 var expressions = node.quasi.expressions.map(acornTransform.getSource);
58 acornTransform.setSource(node, acornTransform.getSource(node.tag) + '(' +
59 [quasi].concat(expressions).join(', ') + ')');
60 }
61 });
62 makeStatic.on('data', this.push.bind(this));
63 makeStatic.on('error', callback);
64 makeStatic.on('end', callback.bind(null, null));
65 makeStatic.end(src);
66 };
67
68 function staticCompileImplementation(jadeSrc, localOptions) {
69 localOptions = localOptions || {};
70 for (var key in options) {
71 if ((key in options) && !(key in localOptions))
72 localOptions[key] = options[key];
73 }
74 localOptions.outputFile = filename;
75 return compileClient(jadeSrc, localOptions);
76 }
77 function staticCompileFileImplementation(jadeFile, localOptions) {
78 localOptions = localOptions || {};
79 for (var key in options) {
80 if ((key in options) && !(key in localOptions))
81 localOptions[key] = options[key];
82 }
83 localOptions.outputFile = filename;
84 return compileFileClient(jadeFile, localOptions);
85 }
86 function staticImplementation(templateLiteral) {
87 if (isTemplateLiteral(templateLiteral)) {
88 return staticCompileImplementation(templateLiteral.raw[0]);
89 } else {
90 return 'throw new Error("Invalid client side argument to react-jade");';
91 }
92 }
93 staticImplementation.compile = staticCompileImplementation;
94 staticImplementation.compileFile = staticCompileFileImplementation;
95 var makeStatic = staticModule({ 'react-jade': staticImplementation }, {
96 vars: {
97 __dirname: path.dirname(filename),
98 __filename: path.resolve(filename),
99 path: path,
100 require: clientRequire
101 }
102 });
103
104 return stream;
105 }
106 if (typeof options === 'string') {
107 var file = options;
108 options = extra || {};
109 return transform(file);
110 } else {
111 options = options || {};
112 return transform;
113 }
114}
115
116function parse(str, options) {
117 var options = options || {};
118 var parser = new Parser(str, options.filename, options);
119 var tokens;
120 try {
121 // Parse
122 tokens = parser.parse();
123 } catch (err) {
124 parser = parser.context();
125 jade.rethrow(err, parser.filename, parser.lexer.lineno, parser.input);
126 }
127 var compiler = new Compiler(tokens);
128
129 var js = 'var fn = function (locals) {' +
130 'function jade_join_classes(val) {' +
131 'return Array.isArray(val) ? val.map(jade_join_classes).filter(function (val) { return val != null && val !== ""; }).join(" ") : val;' +
132 '};' +
133 'var jade_mixins = {};' +
134 'var jade_interp;' +
135 'jade_variables(locals);' +
136 compiler.compile() +
137 '}';
138
139 // Check that the compiled JavaScript code is valid thus far.
140 // uglify-js throws very cryptic errors when it fails to parse code.
141 try {
142 Function('', js);
143 } catch (ex) {
144 console.log(js);
145 throw ex;
146 }
147
148 var ast = uglify.parse(js, {filename: options.filename});
149
150 ast.figure_out_scope();
151 ast = ast.transform(uglify.Compressor({
152 sequences: false, // join consecutive statemets with the “comma operator"
153 properties: true, // optimize property access: a["foo"] → a.foo
154 dead_code: true, // discard unreachable code
155 unsafe: true, // some unsafe optimizations (see below)
156 conditionals: true, // optimize if-s and conditional expressions
157 comparisons: true, // optimize comparisonsx
158 evaluate: true, // evaluate constant expressions
159 booleans: true, // optimize boolean expressions
160 loops: true, // optimize loops
161 unused: true, // drop unused variables/functions
162 hoist_funs: true, // hoist function declarations
163 hoist_vars: false, // hoist variable declarations
164 if_return: true, // optimize if-s followed by return/continue
165 join_vars: false, // join var declarations
166 cascade: true, // try to cascade `right` into `left` in sequences
167 side_effects: true, // drop side-effect-free statements
168 warnings: false, // warn about potentially dangerous optimizations/code
169 global_defs: {} // global definitions));
170 }));
171
172 ast = ast.transform(new JavaScriptCompressor());
173
174 ast.figure_out_scope();
175 var globals = ast.globals.map(function (node, name) {
176 return name;
177 }).filter(function (name) {
178 return name !== 'jade_variables' && name !== 'exports' && name !== 'Array' && name !== 'React';
179 });
180
181 js = ast.print_to_string({
182 beautify: true,
183 comments: true,
184 indent_level: 2
185 });
186 assert(/jade_variables\(locals\)/.test(js));
187
188 js = js.replace(/\n? *jade_variables\(locals\);?/, globals.map(function (g) {
189 return ' var ' + g + ' = ' + JSON.stringify(g) + ' in locals ? locals.' + g + ' : jade_globals_' + g + ';';
190 }).join('\n'));
191 return globals.map(function (g) {
192 return 'var jade_globals_' + g + ' = typeof ' + g + ' === "undefined" ? undefined : ' + g + ';\n';
193 }).join('') + js + ';\nfn.locals = ' + setLocals.toString() + ';\nreturn fn;';
194}
195
196function parseFile(filename, options) {
197 var str = fs.readFileSync(filename, 'utf8').toString();
198 var options = options || {};
199 options.filename = path.resolve(filename);
200 return parse(str, options);
201}
202
203exports.compile = compile;
204function compile(str, options){
205 options = options || { filename: '' };
206 return Function('React', parse(str, options))(React);
207}
208
209exports.compileFile = compileFile;
210function compileFile(filename, options) {
211 return Function('React', parseFile(filename, options))(React);
212}
213
214exports.compileClient = compileClient;
215function compileClient(str, options){
216 options = options || { filename: '' };
217 var react = options.outputFile ? path.relative(path.dirname(options.outputFile), reactRuntimePath) : reactRuntimePath;
218
219 if (options.globalReact) {
220 return '(function (React) {\n ' +
221 parse(str, options).split('\n').join('\n ') +
222 '\n}(React))';
223 } else {
224 return '(function (React) {\n ' +
225 parse(str, options).split('\n').join('\n ') +
226 '\n}(typeof React !== "undefined" ? React : require("' + react.replace(/^([^\.])/, './$1').replace(/\\/g, '/') + '")))';
227 }
228}
229
230exports.compileFileClient = compileFileClient;
231function compileFileClient(filename, options) {
232 var str = fs.readFileSync(filename, 'utf8').toString();
233 var options = options || {};
234 options.filename = path.resolve(filename);
235 return compileClient(str, options);
236}
237
238function setLocals(locals) {
239 var render = this;
240 function newRender(additionalLocals) {
241 var newLocals = {};
242 for (var key in locals) {
243 newLocals[key] = locals[key];
244 }
245 if (additionalLocals) {
246 for (var key in additionalLocals) {
247 newLocals[key] = additionalLocals[key];
248 }
249 }
250 return render.call(this, newLocals);
251 }
252 newRender.locals = setLocals;
253 return newRender;
254}