44 sentryUnpluginFactory ,
55 createRollupDebugIdInjectionHooks ,
66} from "../src" ;
7+ import { containsOnlyImports } from "../src/utils" ;
78
89describe ( "getDebugIdSnippet" , ( ) => {
910 it ( "returns the debugId injection snippet for a passed debugId" , ( ) => {
@@ -14,6 +15,137 @@ describe("getDebugIdSnippet", () => {
1415 } ) ;
1516} ) ;
1617
18+ describe ( "containsOnlyImports" , ( ) => {
19+ describe ( "should return true (import-only code)" , ( ) => {
20+ it . each ( [
21+ [ "empty string" , "" ] ,
22+ [ "whitespace only" , " \n\t " ] ,
23+ [ "side effect import with single quotes" , "import './module.js';" ] ,
24+ [ "side effect import with double quotes" , 'import "./module.js";' ] ,
25+ [ "side effect import with backticks" , "import `./module.js`;" ] ,
26+ [ "side effect import without semicolon" , "import './module.js'" ] ,
27+ [ "default import" , "import foo from './module.js';" ] ,
28+ [ "named import" , "import { foo } from './module.js';" ] ,
29+ [ "named import with alias" , "import { foo as bar } from './module.js';" ] ,
30+ [ "multiple named imports" , "import { foo, bar, baz } from './module.js';" ] ,
31+ [ "namespace import" , "import * as utils from './utils.js';" ] ,
32+ [ "default and named imports" , "import React, { useState } from 'react';" ] ,
33+ [ "re-export all" , "export * from './module.js';" ] ,
34+ [ "re-export named" , "export { foo, bar } from './module.js';" ] ,
35+ [ "re-export with alias" , "export { foo as default } from './module.js';" ] ,
36+ ] ) ( "%s" , ( _ , code ) => {
37+ expect ( containsOnlyImports ( code ) ) . toBe ( true ) ;
38+ } ) ;
39+
40+ it . each ( [
41+ [
42+ "multiple imports" ,
43+ `
44+ import './polyfill.js';
45+ import { helper } from './utils.js';
46+ import config from './config.js';
47+ ` ,
48+ ] ,
49+ [
50+ "imports with line comments" ,
51+ `
52+ // This is a comment
53+ import './module.js';
54+ // Another comment
55+ ` ,
56+ ] ,
57+ [
58+ "imports with block comments" ,
59+ `
60+ /* Block comment */
61+ import './module.js';
62+ /* Multi
63+ line
64+ comment */
65+ ` ,
66+ ] ,
67+ [ "'use strict' with imports" , `"use strict";\nimport './module.js';` ] ,
68+ [ "'use strict' with single quotes" , `'use strict';\nimport './module.js';` ] ,
69+ [
70+ "mixed imports, re-exports, and comments" ,
71+ `
72+ "use strict";
73+ // Entry point facade
74+ import './polyfills.js';
75+ import { init } from './app.js';
76+ /* Re-export for external use */
77+ export * from './types.js';
78+ export { config } from './config.js';
79+ ` ,
80+ ] ,
81+ ] ) ( "%s" , ( _ , code ) => {
82+ expect ( containsOnlyImports ( code ) ) . toBe ( true ) ;
83+ } ) ;
84+ } ) ;
85+
86+ describe ( "should return false (contains substantial code)" , ( ) => {
87+ it . each ( [
88+ [ "variable declaration" , "const x = 1;" ] ,
89+ [ "let declaration" , "let y = 2;" ] ,
90+ [ "var declaration" , "var z = 3;" ] ,
91+ [ "function declaration" , "function foo() {}" ] ,
92+ [ "arrow function" , "const fn = () => {};" ] ,
93+ [ "class declaration" , "class MyClass {}" ] ,
94+ [ "function call" , "console.log('hello');" ] ,
95+ [ "IIFE" , "(function() {})();" ] ,
96+ [ "expression statement" , "1 + 1;" ] ,
97+ [ "object literal" , "({ foo: 'bar' });" ] ,
98+ [ "export declaration (not re-export)" , "export const foo = 1;" ] ,
99+ [ "export default expression" , "export default {};" ] ,
100+ [ "export function" , "export function foo() {}" ] ,
101+ [ "minified bundle code" , `import{a as e}from"./chunk.js";var t=function(){return e()};t();` ] ,
102+ ] ) ( "%s" , ( _ , code ) => {
103+ expect ( containsOnlyImports ( code ) ) . toBe ( false ) ;
104+ } ) ;
105+
106+ // Multi-line code snippets
107+ it . each ( [
108+ [
109+ "import followed by code" ,
110+ `
111+ import { init } from './app.js';
112+ init();
113+ ` ,
114+ ] ,
115+ [
116+ "import with variable declaration" ,
117+ `
118+ import './module.js';
119+ const config = { debug: true };
120+ ` ,
121+ ] ,
122+ [
123+ "import with function declaration" ,
124+ `
125+ import { helper } from './utils.js';
126+ function main() {
127+ helper();
128+ }
129+ ` ,
130+ ] ,
131+ [
132+ "real-world SPA bundle snippet" ,
133+ `
134+ import { createApp } from 'vue';
135+ import App from './App.vue';
136+ import router from './router';
137+
138+ const app = createApp(App);
139+ app.use(router);
140+ app.mount('#app');
141+ ` ,
142+ ] ,
143+ ] ) ( "%s" , ( _ , code ) => {
144+ expect ( containsOnlyImports ( code ) ) . toBe ( false ) ;
145+ } ) ;
146+ } ) ;
147+ } ) ;
148+
17149describe ( "createRollupDebugIdInjectionHooks" , ( ) => {
18150 const hooks = createRollupDebugIdInjectionHooks ( ) ;
19151
@@ -99,6 +231,107 @@ describe("createRollupDebugIdInjectionHooks", () => {
99231 hooks . renderChunk ( codeWithCommentBeyond500B , { fileName : "bundle.js" } )
100232 ) . not . toBeNull ( ) ;
101233 } ) ;
234+
235+ describe ( "HTML facade chunks (MPA vs SPA)" , ( ) => {
236+ // Issue #829: MPA facades should be skipped
237+ // Regression fix: SPA main bundles with HTML facades should NOT be skipped
238+
239+ it . each ( [
240+ [ "empty" , "" ] ,
241+ [ "only side-effect imports" , `import './shared-module.js';` ] ,
242+ [ "only named imports" , `import { foo, bar } from './shared-module.js';` ] ,
243+ [ "only re-exports" , `export * from './shared-module.js';` ] ,
244+ [
245+ "multiple imports and comments" ,
246+ `// This is a facade module
247+ import './moduleA.js';
248+ import { x } from './moduleB.js';
249+ /* block comment */
250+ export * from './moduleC.js';` ,
251+ ] ,
252+ [ "'use strict' and imports only" , `"use strict";\nimport './shared-module.js';` ] ,
253+ [ "query string in facadeModuleId" , `import './shared.js';` , "?query=param" ] ,
254+ [ "hash in facadeModuleId" , `import './shared.js';` , "#hash" ] ,
255+ ] ) ( "should SKIP HTML facade chunks: %s" , ( _ , code , suffix = "" ) => {
256+ const result = hooks . renderChunk ( code , {
257+ fileName : "page1.js" ,
258+ facadeModuleId : `/path/to/page1.html${ suffix } ` ,
259+ } ) ;
260+ expect ( result ) . toBeNull ( ) ;
261+ } ) ;
262+
263+ it ( "should inject into HTML facade with function declarations" , ( ) => {
264+ const result = hooks . renderChunk ( `function main() { console.log("hello"); }` , {
265+ fileName : "index.js" ,
266+ facadeModuleId : "/path/to/index.html" ,
267+ } ) ;
268+ expect ( result ) . not . toBeNull ( ) ;
269+ expect ( result ?. code ) . toMatchInlineSnapshot (
270+ `";{try{(function(){var e=\\"undefined\\"!=typeof window?window:\\"undefined\\"!=typeof global?global:\\"undefined\\"!=typeof globalThis?globalThis:\\"undefined\\"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]=\\"c4c89e04-3658-4874-b25b-07e638185091\\",e._sentryDebugIdIdentifier=\\"sentry-dbid-c4c89e04-3658-4874-b25b-07e638185091\\");})();}catch(e){}};function main() { console.log(\\"hello\\"); }"`
271+ ) ;
272+ } ) ;
273+
274+ it ( "should inject into HTML facade with variable declarations" , ( ) => {
275+ const result = hooks . renderChunk ( `const x = 42;` , {
276+ fileName : "index.js" ,
277+ facadeModuleId : "/path/to/index.html" ,
278+ } ) ;
279+ expect ( result ) . not . toBeNull ( ) ;
280+ expect ( result ?. code ) . toMatchInlineSnapshot (
281+ `";{try{(function(){var e=\\"undefined\\"!=typeof window?window:\\"undefined\\"!=typeof global?global:\\"undefined\\"!=typeof globalThis?globalThis:\\"undefined\\"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]=\\"43e69766-1963-49f2-a291-ff8de60cc652\\",e._sentryDebugIdIdentifier=\\"sentry-dbid-43e69766-1963-49f2-a291-ff8de60cc652\\");})();}catch(e){}};const x = 42;"`
282+ ) ;
283+ } ) ;
284+
285+ it ( "should inject into HTML facade with substantial code (SPA main bundle)" , ( ) => {
286+ const code = `import { initApp } from './app.js';
287+
288+ const config = { debug: true };
289+
290+ function bootstrap() {
291+ initApp(config);
292+ }
293+
294+ bootstrap();` ;
295+ const result = hooks . renderChunk ( code , {
296+ fileName : "index.js" ,
297+ facadeModuleId : "/path/to/index.html" ,
298+ } ) ;
299+ expect ( result ) . not . toBeNull ( ) ;
300+ expect ( result ?. code ) . toMatchInlineSnapshot ( `
301+ ";{try{(function(){var e=\\"undefined\\"!=typeof window?window:\\"undefined\\"!=typeof global?global:\\"undefined\\"!=typeof globalThis?globalThis:\\"undefined\\"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]=\\"d0c4524b-496e-45a4-9852-7558d043ba3c\\",e._sentryDebugIdIdentifier=\\"sentry-dbid-d0c4524b-496e-45a4-9852-7558d043ba3c\\");})();}catch(e){}};import { initApp } from './app.js';
302+
303+ const config = { debug: true };
304+
305+ function bootstrap() {
306+ initApp(config);
307+ }
308+
309+ bootstrap();"
310+ ` ) ;
311+ } ) ;
312+
313+ it ( "should inject into HTML facade with mixed imports and code" , ( ) => {
314+ const result = hooks . renderChunk (
315+ `import './polyfills.js';\nimport { init } from './app.js';\n\ninit();` ,
316+ { fileName : "index.js" , facadeModuleId : "/path/to/index.html" }
317+ ) ;
318+ expect ( result ) . not . toBeNull ( ) ;
319+ expect ( result ?. code ) . toMatchInlineSnapshot ( `
320+ ";{try{(function(){var e=\\"undefined\\"!=typeof window?window:\\"undefined\\"!=typeof global?global:\\"undefined\\"!=typeof globalThis?globalThis:\\"undefined\\"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]=\\"28f0bbaa-9aeb-40c4-98c9-4e44f1d4e175\\",e._sentryDebugIdIdentifier=\\"sentry-dbid-28f0bbaa-9aeb-40c4-98c9-4e44f1d4e175\\");})();}catch(e){}};import './polyfills.js';
321+ import { init } from './app.js';
322+
323+ init();"
324+ ` ) ;
325+ } ) ;
326+
327+ it ( "should inject into regular JS chunks (no HTML facade)" , ( ) => {
328+ const result = hooks . renderChunk ( `console.log("Hello");` , { fileName : "bundle.js" } ) ;
329+ expect ( result ) . not . toBeNull ( ) ;
330+ expect ( result ?. code ) . toMatchInlineSnapshot (
331+ `";{try{(function(){var e=\\"undefined\\"!=typeof window?window:\\"undefined\\"!=typeof global?global:\\"undefined\\"!=typeof globalThis?globalThis:\\"undefined\\"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]=\\"79f18a7f-ca16-4168-9797-906c82058367\\",e._sentryDebugIdIdentifier=\\"sentry-dbid-79f18a7f-ca16-4168-9797-906c82058367\\");})();}catch(e){}};console.log(\\"Hello\\");"`
332+ ) ;
333+ } ) ;
334+ } ) ;
102335 } ) ;
103336} ) ;
104337
0 commit comments