Skip to content

Commit

Permalink
Pass through nonce to the transformed script element (#15671)
Browse files Browse the repository at this point in the history
  • Loading branch information
JLHwung committed Sep 25, 2023
1 parent 4d93bae commit 7e69e32
Show file tree
Hide file tree
Showing 6 changed files with 424 additions and 39 deletions.
3 changes: 2 additions & 1 deletion packages/babel-standalone/package.json
Expand Up @@ -114,7 +114,8 @@
"@babel/preset-flow": "workspace:^",
"@babel/preset-react": "workspace:^",
"@babel/preset-typescript": "workspace:^",
"acorn": "^8.7.0"
"acorn": "^8.7.0",
"jsdom": "^22.1.0"
},
"keywords": [
"babel",
Expand Down
55 changes: 28 additions & 27 deletions packages/babel-standalone/src/transformScriptTags.ts
Expand Up @@ -21,6 +21,8 @@ type CompilationResult = {
loaded: boolean;
content: string | null;
executed: boolean;
// nonce is undefined in browsers that don't support the nonce global attribute
nonce: string | undefined;
// todo: refine plugins/presets
plugins: InputOptions["plugins"];
presets: InputOptions["presets"];
Expand Down Expand Up @@ -95,6 +97,9 @@ function run(transformFn: typeof transform, script: CompilationResult) {
if (script.type) {
scriptEl.setAttribute("type", script.type);
}
if (script.nonce) {
scriptEl.nonce = script.nonce;
}
scriptEl.text = transformCode(transformFn, script);
headEl.appendChild(scriptEl);
}
Expand Down Expand Up @@ -159,64 +164,60 @@ function loadScripts(
transformFn: typeof transform,
scripts: HTMLScriptElement[],
) {
const result: CompilationResult[] = [];
const results: CompilationResult[] = [];
const count = scripts.length;

function check() {
let script, i;

for (i = 0; i < count; i++) {
script = result[i];
for (let i = 0; i < count; i++) {
const result = results[i];

if (script.loaded && !script.executed) {
script.executed = true;
run(transformFn, script);
} else if (!script.loaded && !script.error && !script.async) {
if (result.loaded && !result.executed) {
result.executed = true;
run(transformFn, result);
} else if (!result.loaded && !result.error && !result.async) {
break;
}
}
}

scripts.forEach((script, i) => {
const scriptData = {
for (let i = 0; i < count; i++) {
const script = scripts[i];
const result: CompilationResult = {
// script.async is always true for non-JavaScript script tags
async: script.hasAttribute("async"),
type: script.getAttribute("data-type"),
nonce: script.nonce,
error: false,
executed: false,
plugins: getPluginsOrPresetsFromScript(script, "data-plugins"),
presets: getPluginsOrPresetsFromScript(script, "data-presets"),
loaded: false,
url: null,
content: null,
};
results.push(result);

if (script.src) {
result[i] = {
...scriptData,
content: null,
loaded: false,
url: script.src,
};
result.url = script.src;

load(
script.src,
content => {
result[i].loaded = true;
result[i].content = content;
result.loaded = true;
result.content = content;
check();
},
() => {
result[i].error = true;
result.error = true;
check();
},
);
} else {
result[i] = {
...scriptData,
content: script.innerHTML,
loaded: true,
url: script.getAttribute("data-module") || null,
};
result.url = script.getAttribute("data-module") || null;
result.loaded = true;
result.content = script.innerHTML;
}
});
}

check();
}
Expand Down
57 changes: 57 additions & 0 deletions packages/babel-standalone/test/transform-script-tags.test.js
@@ -0,0 +1,57 @@
import fs from "fs";
import { createRequire } from "module";
import { describeGte } from "$repo-utils";
const require = createRequire(import.meta.url);

describeGte("16.0.0")("transformScriptTags", () => {
let standaloneSource;
let JSDOM;
beforeAll(async () => {
standaloneSource = fs.readFileSync(
new URL("../babel.js", import.meta.url),
"utf8",
);
JSDOM = require("jsdom").JSDOM;
});
it("should transform script element with type 'text/babel'", () => {
const dom = new JSDOM(
`<!DOCTYPE html><head><script>${standaloneSource}</script><script type="text/babel">globalThis ?? window</script></head>`,
{ runScripts: "dangerously" },
);
return new Promise((resolve, reject) => {
dom.window.addEventListener("DOMContentLoaded", () => {
try {
const transformedScriptElement =
dom.window.document.head.children.item(2);
expect(transformedScriptElement.getAttribute("type")).toBeNull();
expect(transformedScriptElement.innerHTML).toContain(
"globalThis !== null && globalThis !== void 0 ? globalThis : window",
);
resolve();
} catch (err) {
reject(err);
}
});
});
});
it("should pass through the nonce attribute to the transformed script element", () => {
const nonceAttribute = "nonce_example";

const dom = new JSDOM(
`<!DOCTYPE html><head><script>${standaloneSource}</script><script type="text/babel" nonce="${nonceAttribute}">globalThis ?? window</script></head>`,
{ runScripts: "dangerously" },
);
return new Promise((resolve, reject) => {
dom.window.addEventListener("DOMContentLoaded", () => {
try {
const transformedScriptElement =
dom.window.document.head.children.item(2);
expect(transformedScriptElement.nonce).toBe(nonceAttribute);
resolve();
} catch (err) {
reject(err);
}
});
});
});
});
3 changes: 3 additions & 0 deletions scripts/repo-utils/index.cjs
Expand Up @@ -44,6 +44,9 @@ if (typeof jest !== "undefined") {
exports.describeESM = USE_ESM ? describe : dummy;
exports.describeBabel7 = process.env.BABEL_8_BREAKING ? dummy : describe;
exports.describeBabel8 = process.env.BABEL_8_BREAKING ? describe : dummy;
exports.describeGte = function (version) {
return semver.gte(process.version, version) ? describe : describe.skip;
};
}

exports.commonJS = function (metaUrl) {
Expand Down
1 change: 1 addition & 0 deletions scripts/repo-utils/index.d.ts
Expand Up @@ -17,3 +17,4 @@ export const itBabel7NoESM: jest.It;
export const itDummy: jest.It;
export const describeBabel7: jest.Describe;
export const describeBabel8: jest.Describe;
export function describeGte(version: string): jest.Describe;

0 comments on commit 7e69e32

Please sign in to comment.