Conversation
✅ Deploy Preview for creative-fairy-df92c4 ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
aklinker1
left a comment
There was a problem hiding this comment.
- The
globalNameproperty is available on all entrypoints - should we restrict this to just content scripts and unlisted scripts in the type definitions?
Only content scripts and unlisted scripts are bundled as IIFEs, so this option is only relevant for them.
- Is there any benefit to having the
globalNameoption available for other entrypoint types?
No, for the above reason.
- How shall we indicate that the default value will switch to the behaviour of
falsein a future WXT version? Would this version change the generated output for other script types too (eg: background, popup), or will we leave them generating a named IIFE.
You don't need to indicate anything in this PR. For the PR that makes the breaking change, add a section to the PR explaining the change and migration path for people.
https://github.com/wxt-dev/wxt/blob/main/CONTRIBUTING.md#breaking-changes-policy
There was a problem hiding this comment.
I don't think we need a full plugin to do this... If I remember correctly, vite will already output a anonymous IIFE if we don't specify a global name?
There was a problem hiding this comment.
I am wrong. We need a plugin like you created!
| * | ||
| * @default true | ||
| */ | ||
| globalName?: string | boolean | ((entrypoint: Entrypoint) => string); |
There was a problem hiding this comment.
Yeah, this needs to be moved to the content script and unlisted scripts types. I think it's fine if you duplicate this or feel free to create a shared base type.
| expect( | ||
| await project.serializeFile( | ||
| '.output/chrome-mv3/content-scripts/content.js', | ||
| ), | ||
| ).toMatchInlineSnapshot(` | ||
| ".output/chrome-mv3/content-scripts/content.js | ||
| ---------------------------------------- | ||
| var content=(function(){"use strict";function E(e){return e}const h={matches:["*://*/*"],main(){}};function s(e,...t){}const u={debug:(...e)=>s(console.debug,...e),log:(...e)=>s(console.log,...e),warn:(...e)=>s(console.warn,...e),error:(...e)=>s(console.error,...e)},l=globalThis.browser?.runtime?.id?globalThis.browser:globalThis.chrome;var g=class d extends Event{static EVENT_NAME=a("wxt:locationchange");constructor(t,n){super(d.EVENT_NAME,{}),this.newUrl=t,this.oldUrl=n}};function a(e){return\`\${l?.runtime?.id}:content:\${e}\`}function v(e){let t,n;return{run(){t==null&&(n=new URL(location.href),t=e.setInterval(()=>{let r=new URL(location.href);r.href!==n.href&&(window.dispatchEvent(new g(r,n)),n=r)},1e3))}}}var m=class c{static SCRIPT_STARTED_MESSAGE_TYPE=a("wxt:content-script-started");isTopFrame=window.self===window.top;abortController;locationWatcher=v(this);receivedMessageIds=new Set;constructor(t,n){this.contentScriptName=t,this.options=n,this.abortController=new AbortController,this.isTopFrame?(this.listenForNewerScripts({ignoreFirstEvent:!0}),this.stopOldScripts()):this.listenForNewerScripts()}get signal(){return this.abortController.signal}abort(t){return this.abortController.abort(t)}get isInvalid(){return l.runtime.id==null&&this.notifyInvalidated(),this.signal.aborted}get isValid(){return!this.isInvalid}onInvalidated(t){return this.signal.addEventListener("abort",t),()=>this.signal.removeEventListener("abort",t)}block(){return new Promise(()=>{})}setInterval(t,n){const r=setInterval(()=>{this.isValid&&t()},n);return this.onInvalidated(()=>clearInterval(r)),r}setTimeout(t,n){const r=setTimeout(()=>{this.isValid&&t()},n);return this.onInvalidated(()=>clearTimeout(r)),r}requestAnimationFrame(t){const n=requestAnimationFrame((...r)=>{this.isValid&&t(...r)});return this.onInvalidated(()=>cancelAnimationFrame(n)),n}requestIdleCallback(t,n){const r=requestIdleCallback((...i)=>{this.signal.aborted||t(...i)},n);return this.onInvalidated(()=>cancelIdleCallback(r)),r}addEventListener(t,n,r,i){n==="wxt:locationchange"&&this.isValid&&this.locationWatcher.run(),t.addEventListener?.(n.startsWith("wxt:")?a(n):n,r,{...i,signal:this.signal})}notifyInvalidated(){this.abort("Content script context invalidated"),u.debug(\`Content script "\${this.contentScriptName}" context invalidated\`)}stopOldScripts(){window.postMessage({type:c.SCRIPT_STARTED_MESSAGE_TYPE,contentScriptName:this.contentScriptName,messageId:Math.random().toString(36).slice(2)},"*")}verifyScriptStartedEvent(t){const n=t.data?.type===c.SCRIPT_STARTED_MESSAGE_TYPE,r=t.data?.contentScriptName===this.contentScriptName,i=!this.receivedMessageIds.has(t.data?.messageId);return n&&r&&i}listenForNewerScripts(t){let n=!0;const r=i=>{if(this.verifyScriptStartedEvent(i)){this.receivedMessageIds.add(i.data.messageId);const S=n;if(n=!1,S&&t?.ignoreFirstEvent)return;this.notifyInvalidated()}};addEventListener("message",r),this.onInvalidated(()=>removeEventListener("message",r))}};function b(){}function o(e,...t){}const p={debug:(...e)=>o(console.debug,...e),log:(...e)=>o(console.log,...e),warn:(...e)=>o(console.warn,...e),error:(...e)=>o(console.error,...e)};var w=(async()=>{try{const{main:e,...t}=h;return await e(new m("content",t))}catch(e){throw p.error('The content script "content" crashed on startup!',e),e}})();return w})(); | ||
| content;" | ||
| `); |
There was a problem hiding this comment.
What about using this for the expectation?
| expect( | |
| await project.serializeFile( | |
| '.output/chrome-mv3/content-scripts/content.js', | |
| ), | |
| ).toMatchInlineSnapshot(` | |
| ".output/chrome-mv3/content-scripts/content.js | |
| ---------------------------------------- | |
| var content=(function(){"use strict";function E(e){return e}const h={matches:["*://*/*"],main(){}};function s(e,...t){}const u={debug:(...e)=>s(console.debug,...e),log:(...e)=>s(console.log,...e),warn:(...e)=>s(console.warn,...e),error:(...e)=>s(console.error,...e)},l=globalThis.browser?.runtime?.id?globalThis.browser:globalThis.chrome;var g=class d extends Event{static EVENT_NAME=a("wxt:locationchange");constructor(t,n){super(d.EVENT_NAME,{}),this.newUrl=t,this.oldUrl=n}};function a(e){return\`\${l?.runtime?.id}:content:\${e}\`}function v(e){let t,n;return{run(){t==null&&(n=new URL(location.href),t=e.setInterval(()=>{let r=new URL(location.href);r.href!==n.href&&(window.dispatchEvent(new g(r,n)),n=r)},1e3))}}}var m=class c{static SCRIPT_STARTED_MESSAGE_TYPE=a("wxt:content-script-started");isTopFrame=window.self===window.top;abortController;locationWatcher=v(this);receivedMessageIds=new Set;constructor(t,n){this.contentScriptName=t,this.options=n,this.abortController=new AbortController,this.isTopFrame?(this.listenForNewerScripts({ignoreFirstEvent:!0}),this.stopOldScripts()):this.listenForNewerScripts()}get signal(){return this.abortController.signal}abort(t){return this.abortController.abort(t)}get isInvalid(){return l.runtime.id==null&&this.notifyInvalidated(),this.signal.aborted}get isValid(){return!this.isInvalid}onInvalidated(t){return this.signal.addEventListener("abort",t),()=>this.signal.removeEventListener("abort",t)}block(){return new Promise(()=>{})}setInterval(t,n){const r=setInterval(()=>{this.isValid&&t()},n);return this.onInvalidated(()=>clearInterval(r)),r}setTimeout(t,n){const r=setTimeout(()=>{this.isValid&&t()},n);return this.onInvalidated(()=>clearTimeout(r)),r}requestAnimationFrame(t){const n=requestAnimationFrame((...r)=>{this.isValid&&t(...r)});return this.onInvalidated(()=>cancelAnimationFrame(n)),n}requestIdleCallback(t,n){const r=requestIdleCallback((...i)=>{this.signal.aborted||t(...i)},n);return this.onInvalidated(()=>cancelIdleCallback(r)),r}addEventListener(t,n,r,i){n==="wxt:locationchange"&&this.isValid&&this.locationWatcher.run(),t.addEventListener?.(n.startsWith("wxt:")?a(n):n,r,{...i,signal:this.signal})}notifyInvalidated(){this.abort("Content script context invalidated"),u.debug(\`Content script "\${this.contentScriptName}" context invalidated\`)}stopOldScripts(){window.postMessage({type:c.SCRIPT_STARTED_MESSAGE_TYPE,contentScriptName:this.contentScriptName,messageId:Math.random().toString(36).slice(2)},"*")}verifyScriptStartedEvent(t){const n=t.data?.type===c.SCRIPT_STARTED_MESSAGE_TYPE,r=t.data?.contentScriptName===this.contentScriptName,i=!this.receivedMessageIds.has(t.data?.messageId);return n&&r&&i}listenForNewerScripts(t){let n=!0;const r=i=>{if(this.verifyScriptStartedEvent(i)){this.receivedMessageIds.add(i.data.messageId);const S=n;if(n=!1,S&&t?.ignoreFirstEvent)return;this.notifyInvalidated()}};addEventListener("message",r),this.onInvalidated(()=>removeEventListener("message",r))}};function b(){}function o(e,...t){}const p={debug:(...e)=>o(console.debug,...e),log:(...e)=>o(console.log,...e),warn:(...e)=>o(console.warn,...e),error:(...e)=>o(console.error,...e)};var w=(async()=>{try{const{main:e,...t}=h;return await e(new m("content",t))}catch(e){throw p.error('The content script "content" crashed on startup!',e),e}})();return w})(); | |
| content;" | |
| `); | |
| const output = await project.serializeFile( | |
| '.output/chrome-mv3/content-scripts/content.js', | |
| ) | |
| expect(output).toMatch(/^var content=[\s\S]*^content;$/gm); |
If a test doesn't require the full file to match, I'd prefer we narrow it down to only what is required. That way if something unrelated to the global name changes and effects this test, it still passes.
Overview
Implements #2004
The IIFE name is not currently configurable for content scripts and unlisted scripts. The variable name can be important when inserting scripts with world=MAIN or directly via a
<script>tag to ensure there is no conflict with an existing variable name on the page.This PR is a followup to the discussion in #1897 where we decided to add a
globalNameoption.Questions:
globalNameproperty is available on all entrypoints - should we restrict this to just content scripts and unlisted scripts in the type definitions?globalNameoption available for other entrypoint types?falsein a future WXT version? Would this version change the generated output for other script types too (eg: background, popup), or will we leave them generating a named IIFE.Manual Testing
Specify the
globalNameoption on a content script or unlisted script entrypoint and check the generated JS output matches what's expected for the given option.