packages/babel-standalone/src/transformScriptTags.js
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of the React source tree.
*/
const scriptTypes = ["text/jsx", "text/babel"];
let headEl;
let inlineScriptCount = 0;
/**
* Actually transform the code.
*/
function transformCode(transformFn, script) {
let source;
if (script.url != null) {
source = script.url;
} else {
source = "Inline Babel script";
inlineScriptCount++;
if (inlineScriptCount > 1) {
source += " (" + inlineScriptCount + ")";
}
}
return transformFn(script.content, buildBabelOptions(script, source)).code;
}
/**
* Builds the Babel options for transforming the specified script, using some
* sensible default presets and plugins if none were explicitly provided.
*/
function buildBabelOptions(script, filename) {
return {
filename,
presets: script.presets || ["react", "es2015"],
plugins: script.plugins || [
"proposal-class-properties",
"proposal-object-rest-spread",
"transform-flow-strip-types",
],
sourceMaps: "inline",
sourceFileName: filename,
};
}
/**
* Appends a script element at the end of the <head> with the content of code,
* after transforming it.
*/
function run(transformFn, script) {
const scriptEl = document.createElement("script");
if (script.type) {
scriptEl.setAttribute("type", script.type);
}
scriptEl.text = transformCode(transformFn, script);
headEl.appendChild(scriptEl);
}
/**
* Load script from the provided url and pass the content to the callback.
*/
function load(url, successCallback, errorCallback) {
const xhr = new XMLHttpRequest();
// async, however scripts will be executed in the order they are in the
// DOM to mirror normal script loading.
xhr.open("GET", url, true);
if ("overrideMimeType" in xhr) {
xhr.overrideMimeType("text/plain");
}
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 0 || xhr.status === 200) {
successCallback(xhr.responseText);
} else {
errorCallback();
throw new Error("Could not load " + url);
}
}
};
return xhr.send(null);
}
/**
* Converts a comma-separated data attribute string into an array of values. If
* the string is empty, returns an empty array. If the string is not defined,
* returns null.
*/
function getPluginsOrPresetsFromScript(script, attributeName) {
const rawValue = script.getAttribute(attributeName);
if (rawValue === "") {
// Empty string means to not load ANY presets or plugins
return [];
}
if (!rawValue) {
// Any other falsy value (null, undefined) means we're not overriding this
// setting, and should use the default.
return null;
}
return rawValue.split(",").map(item => item.trim());
}
/**
* Loop over provided script tags and get the content, via innerHTML if an
* inline script, or by using XHR. Transforms are applied if needed. The scripts
* are executed in the order they are found on the page.
*/
function loadScripts(transformFn, scripts) {
const result = [];
const count = scripts.length;
function check() {
let script, i;
for (i = 0; i < count; i++) {
script = result[i];
if (script.loaded && !script.executed) {
script.executed = true;
run(transformFn, script);
} else if (!script.loaded && !script.error && !script.async) {
break;
}
}
}
scripts.forEach((script, i) => {
const scriptData = {
// script.async is always true for non-JavaScript script tags
async: script.hasAttribute("async"),
type: script.getAttribute("data-type"),
error: false,
executed: false,
plugins: getPluginsOrPresetsFromScript(script, "data-plugins"),
presets: getPluginsOrPresetsFromScript(script, "data-presets"),
};
if (script.src) {
result[i] = {
...scriptData,
content: null,
loaded: false,
url: script.src,
};
load(
script.src,
content => {
result[i].loaded = true;
result[i].content = content;
check();
},
() => {
result[i].error = true;
check();
},
);
} else {
result[i] = {
...scriptData,
content: script.innerHTML,
loaded: true,
url: script.getAttribute("data-module") || null,
};
}
});
check();
}
/**
* Run script tags with type="text/jsx".
* @param {Array} scriptTags specify script tags to run, run all in the <head> if not given
*/
export function runScripts(transformFn, scripts) {
headEl = document.getElementsByTagName("head")[0];
if (!scripts) {
scripts = document.getElementsByTagName("script");
}
// Array.prototype.slice cannot be used on NodeList on IE8
const jsxScripts = [];
for (let i = 0; i < scripts.length; i++) {
const script = scripts.item(i);
// Support the old type="text/jsx;harmony=true"
const type = script.type.split(";")[0];
if (scriptTypes.indexOf(type) !== -1) {
jsxScripts.push(script);
}
}
if (jsxScripts.length === 0) {
return;
}
console.warn(
"You are using the in-browser Babel transformer. Be sure to precompile " +
"your scripts for production - https://babeljs.io/docs/setup/",
);
loadScripts(transformFn, jsxScripts);
}