UNPKG

5.64 kBJavaScriptView Raw
1/**
2 * Copyright 2013-2015, Facebook, Inc.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of the React source tree. An additional
7 * grant of patent rights can be found in the PATENTS file in the same directory.
8 */
9
10const scriptTypes = [
11 'text/jsx',
12 'text/babel',
13];
14
15let headEl;
16let inlineScriptCount = 0;
17
18/**
19 * Actually transform the code.
20 */
21function transformCode(transformFn, script) {
22 let source;
23 if (script.url != null) {
24 source = script.url;
25 } else {
26 source = 'Inline Babel script';
27 inlineScriptCount++;
28 if (inlineScriptCount > 1) {
29 source += ' (' + inlineScriptCount + ')';
30 }
31 }
32
33 return transformFn(
34 script.content,
35 {
36 filename: source,
37 ...buildBabelOptions(script),
38 }
39 ).code;
40}
41
42/**
43 * Builds the Babel options for transforming the specified script, using some
44 * sensible default presets and plugins if none were explicitly provided.
45 */
46function buildBabelOptions(script) {
47 return {
48 presets: script.presets || [
49 'react',
50 'es2015',
51 ],
52 plugins: script.plugins || [
53 'transform-class-properties',
54 'transform-object-rest-spread',
55 'transform-flow-strip-types',
56 ],
57 sourceMaps: 'inline',
58 }
59}
60
61
62/**
63 * Appends a script element at the end of the <head> with the content of code,
64 * after transforming it.
65 */
66function run(transformFn, script) {
67 const scriptEl = document.createElement('script');
68 scriptEl.text = transformCode(transformFn, script);
69 headEl.appendChild(scriptEl);
70}
71
72/**
73 * Load script from the provided url and pass the content to the callback.
74 */
75function load(url, successCallback, errorCallback) {
76 const xhr = new XMLHttpRequest();
77
78 // async, however scripts will be executed in the order they are in the
79 // DOM to mirror normal script loading.
80 xhr.open('GET', url, true);
81 if ('overrideMimeType' in xhr) {
82 xhr.overrideMimeType('text/plain');
83 }
84 xhr.onreadystatechange = function() {
85 if (xhr.readyState === 4) {
86 if (xhr.status === 0 || xhr.status === 200) {
87 successCallback(xhr.responseText);
88 } else {
89 errorCallback();
90 throw new Error('Could not load ' + url);
91 }
92 }
93 };
94 return xhr.send(null);
95}
96
97/**
98 * Converts a comma-separated data attribute string into an array of values. If
99 * the string is empty, returns an empty array. If the string is not defined,
100 * returns null.
101 */
102function getPluginsOrPresetsFromScript(script, attributeName) {
103 const rawValue = script.getAttribute(attributeName);
104 if (rawValue === '') {
105 // Empty string means to not load ANY presets or plugins
106 return [];
107 }
108 if (!rawValue) {
109 // Any other falsy value (null, undefined) means we're not overriding this
110 // setting, and should use the default.
111 return null;
112 }
113 return rawValue.split(',').map(item => item.trim());
114}
115
116/**
117 * Loop over provided script tags and get the content, via innerHTML if an
118 * inline script, or by using XHR. Transforms are applied if needed. The scripts
119 * are executed in the order they are found on the page.
120 */
121function loadScripts(transformFn, scripts) {
122 const result = [];
123 const count = scripts.length;
124
125 function check() {
126 var script, i;
127
128 for (i = 0; i < count; i++) {
129 script = result[i];
130
131 if (script.loaded && !script.executed) {
132 script.executed = true;
133 run(transformFn, script);
134 } else if (!script.loaded && !script.error && !script.async) {
135 break;
136 }
137 }
138 }
139
140 scripts.forEach((script, i) => {
141 const scriptData = {
142 // script.async is always true for non-JavaScript script tags
143 async: script.hasAttribute('async'),
144 error: false,
145 executed: false,
146 plugins: getPluginsOrPresetsFromScript(script, 'data-plugins'),
147 presets: getPluginsOrPresetsFromScript(script, 'data-presets'),
148 };
149
150 if (script.src) {
151 result[i] = {
152 ...scriptData,
153 content: null,
154 loaded: false,
155 url: script.src,
156 };
157
158 load(
159 script.src,
160 content => {
161 result[i].loaded = true;
162 result[i].content = content;
163 check();
164 },
165 () => {
166 result[i].error = true;
167 check();
168 }
169 );
170 } else {
171 result[i] = {
172 ...scriptData,
173 content: script.innerHTML,
174 loaded: true,
175 url: null,
176 };
177 }
178 });
179
180 check();
181}
182
183/**
184 * Run script tags with type="text/jsx".
185 * @param {Array} scriptTags specify script tags to run, run all in the <head> if not given
186 */
187export function runScripts(transformFn, scripts) {
188 headEl = document.getElementsByTagName('head')[0];
189 if (!scripts) {
190 scripts = document.getElementsByTagName('script');
191 }
192
193 // Array.prototype.slice cannot be used on NodeList on IE8
194 const jsxScripts = [];
195 for (let i = 0; i < scripts.length; i++) {
196 const script = scripts.item(i);
197 // Support the old type="text/jsx;harmony=true"
198 const type = script.type.split(';')[0];
199 if (scriptTypes.indexOf(type) !== -1) {
200 jsxScripts.push(script);
201 }
202 }
203
204 if (jsxScripts.length === 0) {
205 return;
206 }
207
208 console.warn(
209 'You are using the in-browser Babel transformer. Be sure to precompile ' +
210 'your scripts for production - https://babeljs.io/docs/setup/'
211 );
212
213 loadScripts(transformFn, jsxScripts);
214}