Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 44 additions & 69 deletions packages/bundler-plugin-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,63 +182,59 @@ export function sentryCliBinaryExists(): boolean {
return fs.existsSync(SentryCli.getPath());
}

// We need to be careful not to inject the snippet before any `"use strict";`s.
// As an additional complication `"use strict";`s may come after any number of comments.
const COMMENT_USE_STRICT_REGEX =
// Note: CodeQL complains that this regex potentially has n^2 runtime. This likely won't affect realistic files.
/^(?:\s*|\/\*(?:.|\r|\n)*?\*\/|\/\/.*[\n\r])*(?:"[^"]*";|'[^']*';)?/;

/**
* Simplified `renderChunk` hook type from Rollup.
* We can't reference the type directly because the Vite plugin complains
* about type mismatches
*/
type RenderChunkHook = (
code: string,
chunk: {
fileName: string;
}
) => {
code: string;
map: SourceMap;
} | null;

export function createRollupReleaseInjectionHooks(injectionCode: string): {
resolveId: UnpluginOptions["resolveId"];
load: UnpluginOptions["load"];
transform: UnpluginOptions["transform"];
renderChunk: RenderChunkHook;
} {
const virtualReleaseInjectionFileId = "\0sentry-release-injection-file";
return {
resolveId(id: string) {
if (id === virtualReleaseInjectionFileId) {
return {
id: virtualReleaseInjectionFileId,
external: false,
moduleSideEffects: true,
};
} else {
return null;
}
},

load(id: string) {
if (id === virtualReleaseInjectionFileId) {
return injectionCode;
} else {
return null;
}
},

transform(code: string, id: string) {
if (id === virtualReleaseInjectionFileId) {
return null;
}

// id may contain query and hash which will trip up our file extension logic below
const idWithoutQueryAndHash = stripQueryAndHashFromPath(id);

if (idWithoutQueryAndHash.match(/\\node_modules\\|\/node_modules\//)) {
return null;
}

renderChunk(code: string, chunk: { fileName: string }) {
if (
![".js", ".ts", ".jsx", ".tsx", ".mjs"].some((ending) =>
idWithoutQueryAndHash.endsWith(ending)
// chunks could be any file (html, md, ...)
[".js", ".mjs", ".cjs"].some((ending) =>
stripQueryAndHashFromPath(chunk.fileName).endsWith(ending)
)
) {
return null;
}
const ms = new MagicString(code, { filename: chunk.fileName });

const ms = new MagicString(code);
const match = code.match(COMMENT_USE_STRICT_REGEX)?.[0];

// Appending instead of prepending has less probability of mucking with user's source maps.
// Luckily import statements get hoisted to the top anyways.
ms.append(`\n\n;import "${virtualReleaseInjectionFileId}";`);
if (match) {
// Add injected code after any comments or "use strict" at the beginning of the bundle.
ms.appendLeft(match.length, injectionCode);
} else {
// ms.replace() doesn't work when there is an empty string match (which happens if
// there is neither, a comment, nor a "use strict" at the top of the chunk) so we
// need this special case here.
ms.prepend(injectionCode);
}

return {
code: ms.toString(),
map: ms.generateMap({ hires: "boundary" }),
};
return {
code: ms.toString(),
map: ms.generateMap({ file: chunk.fileName, hires: "boundary" }),
};
} else {
return null; // returning null means not modifying the chunk at all
}
},
};
}
Expand All @@ -253,27 +249,6 @@ export function createRollupBundleSizeOptimizationHooks(replacementValues: Sentr
};
}

// We need to be careful not to inject the snippet before any `"use strict";`s.
// As an additional complication `"use strict";`s may come after any number of comments.
const COMMENT_USE_STRICT_REGEX =
// Note: CodeQL complains that this regex potentially has n^2 runtime. This likely won't affect realistic files.
/^(?:\s*|\/\*(?:.|\r|\n)*?\*\/|\/\/.*[\n\r])*(?:"[^"]*";|'[^']*';)?/;

/**
* Simplified `renderChunk` hook type from Rollup.
* We can't reference the type directly because the Vite plugin complains
* about type mismatches
*/
type RenderChunkHook = (
code: string,
chunk: {
fileName: string;
}
) => {
code: string;
map: SourceMap;
} | null;

export function createRollupDebugIdInjectionHooks(): {
renderChunk: RenderChunkHook;
} {
Expand Down
46 changes: 8 additions & 38 deletions packages/bundler-plugin-core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,28 +314,17 @@
}): string {
// The code below is mostly ternary operators because it saves bundle size.
// The checks are to support as many environments as possible. (Node.js, Browser, webworkers, etc.)
let code = `(function(){
var _global =
typeof window !== 'undefined' ?
window :
typeof global !== 'undefined' ?
global :
typeof globalThis !== 'undefined' ?
globalThis :
typeof self !== 'undefined' ?
self :
{};

_global.SENTRY_RELEASE={id:${JSON.stringify(release)}};`;
let code = `!function(){var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};`;

code += `e.SENTRY_RELEASE={id:${JSON.stringify(release)}};`;

if (injectBuildInformation) {
const buildInfo = getBuildInformation();

code += `
_global.SENTRY_BUILD_INFO=${JSON.stringify(buildInfo)};`;
code += `e.SENTRY_BUILD_INFO=${JSON.stringify(buildInfo)};`;
}

code += "})();";
code += "}();";

return code;
}
Expand All @@ -345,28 +334,9 @@
// The code below is mostly ternary operators because it saves bundle size.
// The checks are to support as many environments as possible. (Node.js, Browser, webworkers, etc.)
// We are merging the metadata objects in case modules are bundled twice with the plugin
return `(function(){
var _sentryModuleMetadataGlobal =
typeof window !== "undefined"
? window
: typeof global !== "undefined"
? global
: typeof globalThis !== "undefined"
? globalThis
: typeof self !== "undefined"
? self
: {};

_sentryModuleMetadataGlobal._sentryModuleMetadata =
_sentryModuleMetadataGlobal._sentryModuleMetadata || {};

_sentryModuleMetadataGlobal._sentryModuleMetadata[new _sentryModuleMetadataGlobal.Error().stack] =
Object.assign(
{},
_sentryModuleMetadataGlobal._sentryModuleMetadata[new _sentryModuleMetadataGlobal.Error().stack],
${JSON.stringify(metadata)}
);
})();`;
return `!function(){var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e._sentryModuleMetadata=e._sentryModuleMetadata||{},e._sentryModuleMetadata[(new e.Error).stack]=Object.assign({},e._sentryModuleMetadata[(new e.Error).stack],${JSON.stringify(
metadata
)})}();`;
}

export function getBuildInformation(): {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`generateGlobalInjectorCode generates code with release 1`] = `"!function(){var e=\\"undefined\\"!=typeof window?window:\\"undefined\\"!=typeof global?global:\\"undefined\\"!=typeof globalThis?globalThis:\\"undefined\\"!=typeof self?self:{};e.SENTRY_RELEASE={id:\\"1.2.3\\"};}();"`;

exports[`generateGlobalInjectorCode generates code with release and build information 1`] = `"!function(){var e=\\"undefined\\"!=typeof window?window:\\"undefined\\"!=typeof global?global:\\"undefined\\"!=typeof globalThis?globalThis:\\"undefined\\"!=typeof self?self:{};e.SENTRY_RELEASE={id:\\"1.2.3\\"};e.SENTRY_BUILD_INFO={\\"deps\\":[\\"myDep\\",\\"rollup\\"],\\"depsVersions\\":{\\"rollup\\":3},\\"nodeVersion\\":18};}();"`;

exports[`generateModuleMetadataInjectorCode generates code with empty metadata object 1`] = `"!function(){var e=\\"undefined\\"!=typeof window?window:\\"undefined\\"!=typeof global?global:\\"undefined\\"!=typeof globalThis?globalThis:\\"undefined\\"!=typeof self?self:{};e._sentryModuleMetadata=e._sentryModuleMetadata||{},e._sentryModuleMetadata[(new e.Error).stack]=Object.assign({},e._sentryModuleMetadata[(new e.Error).stack],{})}();"`;

exports[`generateModuleMetadataInjectorCode generates code with metadata object 1`] = `"!function(){var e=\\"undefined\\"!=typeof window?window:\\"undefined\\"!=typeof global?global:\\"undefined\\"!=typeof globalThis?globalThis:\\"undefined\\"!=typeof self?self:{};e._sentryModuleMetadata=e._sentryModuleMetadata||{},e._sentryModuleMetadata[(new e.Error).stack]=Object.assign({},e._sentryModuleMetadata[(new e.Error).stack],{\\"file1.js\\":{\\"foo\\":\\"bar\\"},\\"file2.js\\":{\\"bar\\":\\"baz\\"}})}();"`;
83 changes: 4 additions & 79 deletions packages/bundler-plugin-core/test/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,21 +227,7 @@ describe("generateGlobalInjectorCode", () => {
injectBuildInformation: false,
});

expect(generatedCode).toMatchInlineSnapshot(`
"(function(){
var _global =
typeof window !== 'undefined' ?
window :
typeof global !== 'undefined' ?
global :
typeof globalThis !== 'undefined' ?
globalThis :
typeof self !== 'undefined' ?
self :
{};

_global.SENTRY_RELEASE={id:\\"1.2.3\\"};})();"
`);
expect(generatedCode).toMatchSnapshot();
});

it("generates code with release and build information", () => {
Expand All @@ -262,52 +248,14 @@ describe("generateGlobalInjectorCode", () => {
injectBuildInformation: true,
});

expect(generatedCode).toMatchInlineSnapshot(`
"(function(){
var _global =
typeof window !== 'undefined' ?
window :
typeof global !== 'undefined' ?
global :
typeof globalThis !== 'undefined' ?
globalThis :
typeof self !== 'undefined' ?
self :
{};

_global.SENTRY_RELEASE={id:\\"1.2.3\\"};
_global.SENTRY_BUILD_INFO={\\"deps\\":[\\"myDep\\",\\"rollup\\"],\\"depsVersions\\":{\\"rollup\\":3},\\"nodeVersion\\":18};})();"
`);
expect(generatedCode).toMatchSnapshot();
});
});

describe("generateModuleMetadataInjectorCode", () => {
it("generates code with empty metadata object", () => {
const generatedCode = generateModuleMetadataInjectorCode({});
expect(generatedCode).toMatchInlineSnapshot(`
"(function(){
var _sentryModuleMetadataGlobal =
typeof window !== \\"undefined\\"
? window
: typeof global !== \\"undefined\\"
? global
: typeof globalThis !== \\"undefined\\"
? globalThis
: typeof self !== \\"undefined\\"
? self
: {};

_sentryModuleMetadataGlobal._sentryModuleMetadata =
_sentryModuleMetadataGlobal._sentryModuleMetadata || {};

_sentryModuleMetadataGlobal._sentryModuleMetadata[new _sentryModuleMetadataGlobal.Error().stack] =
Object.assign(
{},
_sentryModuleMetadataGlobal._sentryModuleMetadata[new _sentryModuleMetadataGlobal.Error().stack],
{}
);
})();"
`);
expect(generatedCode).toMatchSnapshot();
});

it("generates code with metadata object", () => {
Expand All @@ -319,29 +267,6 @@ describe("generateModuleMetadataInjectorCode", () => {
bar: "baz",
},
});
expect(generatedCode).toMatchInlineSnapshot(`
"(function(){
var _sentryModuleMetadataGlobal =
typeof window !== \\"undefined\\"
? window
: typeof global !== \\"undefined\\"
? global
: typeof globalThis !== \\"undefined\\"
? globalThis
: typeof self !== \\"undefined\\"
? self
: {};

_sentryModuleMetadataGlobal._sentryModuleMetadata =
_sentryModuleMetadataGlobal._sentryModuleMetadata || {};

_sentryModuleMetadataGlobal._sentryModuleMetadata[new _sentryModuleMetadataGlobal.Error().stack] =
Object.assign(
{},
_sentryModuleMetadataGlobal._sentryModuleMetadata[new _sentryModuleMetadataGlobal.Error().stack],
{\\"file1.js\\":{\\"foo\\":\\"bar\\"},\\"file2.js\\":{\\"bar\\":\\"baz\\"}}
);
})();"
`);
expect(generatedCode).toMatchSnapshot();
});
});
Loading