1 | 'use strict';
|
2 |
|
3 | var fs = require('fs');
|
4 | var path = require('path');
|
5 | var assert = require('assert');
|
6 | var Transform = require('stream').Transform;
|
7 | var Parser = require('jade/lib/parser.js');
|
8 | var jade = require('jade/lib/runtime.js');
|
9 | var React = require('react');
|
10 | var staticModule = require('static-module');
|
11 | var resolve = require('resolve');
|
12 | var uglify = require('uglify-js');
|
13 | var acornTransform = require('./lib/acorn-transform.js');
|
14 | var Compiler = require('./lib/compiler.js');
|
15 | var JavaScriptCompressor = require('./lib/java-script-compressor.js');
|
16 |
|
17 | var reactRuntimePath = require.resolve('react');
|
18 |
|
19 | function 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 |
|
25 | exports = (module.exports = browserifySupport);
|
26 | function 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 |
|
116 | function parse(str, options) {
|
117 | var options = options || {};
|
118 | var parser = new Parser(str, options.filename, options);
|
119 | var tokens;
|
120 | try {
|
121 |
|
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 |
|
140 |
|
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,
|
153 | properties: true,
|
154 | dead_code: true,
|
155 | unsafe: true,
|
156 | conditionals: true,
|
157 | comparisons: true,
|
158 | evaluate: true,
|
159 | booleans: true,
|
160 | loops: true,
|
161 | unused: true,
|
162 | hoist_funs: true,
|
163 | hoist_vars: false,
|
164 | if_return: true,
|
165 | join_vars: false,
|
166 | cascade: true,
|
167 | side_effects: true,
|
168 | warnings: false,
|
169 | global_defs: {}
|
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 |
|
196 | function 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 |
|
203 | exports.compile = compile;
|
204 | function compile(str, options){
|
205 | options = options || { filename: '' };
|
206 | return Function('React', parse(str, options))(React);
|
207 | }
|
208 |
|
209 | exports.compileFile = compileFile;
|
210 | function compileFile(filename, options) {
|
211 | return Function('React', parseFile(filename, options))(React);
|
212 | }
|
213 |
|
214 | exports.compileClient = compileClient;
|
215 | function 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 |
|
230 | exports.compileFileClient = compileFileClient;
|
231 | function 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 |
|
238 | function 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 | }
|