extension-develop
Version:
The develop step of Extension.js
1,347 lines (1,166 loc) • 66.8 kB
JavaScript
"use strict";
var __webpack_require__ = {};
(()=>{
__webpack_require__.d = (exports1, definition)=>{
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
enumerable: true,
get: definition[key]
});
};
})();
(()=>{
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
})();
(()=>{
__webpack_require__.r = (exports1)=>{
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
value: 'Module'
});
Object.defineProperty(exports1, '__esModule', {
value: true
});
};
})();
var __webpack_exports__ = {};
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {
default: ()=>add_content_script_wrapper
});
const external_path_namespaceObject = require("path");
const external_fs_namespaceObject = require("fs");
const external_loader_utils_namespaceObject = require("loader-utils");
const external_schema_utils_namespaceObject = require("schema-utils");
function extractCSSImports(source) {
const cssImports = [];
const lines = source.split('\n');
const cssImportPatterns = [
/^\s*import\s+['"]([^'"]*\.(?:css|scss|sass|less|module\.css))['"]/,
/^\s*import\s+['"]([^'"]*\.(?:css|scss|sass|less|module\.css))['"]\s*;?\s*$/,
/^\s*import\s+['"]([^'"]*\.(?:css|scss|sass|less|module\.css))['"]\s*from\s+['"][^'"]*['"]/
];
for (const line of lines){
const trimmedLine = line.trim();
if (!(trimmedLine.startsWith('//') || trimmedLine.startsWith('/*'))) for (const pattern of cssImportPatterns){
const match = pattern.exec(line);
if (match) {
const cssPath = match[1];
if (cssPath && !cssImports.includes(cssPath)) cssImports.push(cssPath);
}
}
}
return cssImports;
}
function generateReactWrapperCode(source, resourcePath) {
external_path_namespaceObject.basename(resourcePath, external_path_namespaceObject.extname(resourcePath));
const cssImports = extractCSSImports(source);
const resourceDir = external_path_namespaceObject.dirname(resourcePath);
if ('development' === process.env.EXTENSION_ENV) console.log("[Extension.js] Detected React framework with CSS imports:", cssImports);
const cssContentMap = {};
for (const cssImport of cssImports)try {
const cssPath = external_path_namespaceObject.resolve(resourceDir, cssImport);
if (external_fs_namespaceObject.existsSync(cssPath)) {
const cssContent = external_fs_namespaceObject.readFileSync(cssPath, 'utf-8');
const escapedCSS = cssContent.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\${/g, '\\${').replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
cssContentMap[cssImport] = escapedCSS;
if ('development' === process.env.EXTENSION_ENV) console.log(`[Extension.js] Read CSS content for ${cssImport}, length: ${cssContent.length}`);
} else if ('development' === process.env.EXTENSION_ENV) console.warn(`[Extension.js] CSS file not found: ${cssPath}`);
} catch (error) {
if ('development' === process.env.EXTENSION_ENV) console.warn(`[Extension.js] Failed to read CSS file ${cssImport}:`, error);
}
const wrapperCode = `
// React Content Script Wrapper - Auto-generated by Extension.js
// This wrapper provides Shadow DOM isolation and CSS injection for React content scripts
// Original React content script source (directive processed)
${source.replace(/'use shadow-dom'/g, "// 'use shadow-dom'").replace(/"use shadow-dom"/g, '// "use shadow-dom"')}
// Content script options interface
export interface ContentScriptOptions {
rootElement?: string
rootClassName?: string
stylesheets?: string[]
}
// Content script instance interface
export interface ContentScriptInstance {
mount: (container: HTMLElement) => void
unmount: () => void
}
// React Content script wrapper class
class ReactContentScriptWrapper {
private rootElement: HTMLElement | null = null
private shadowRoot: ShadowRoot | null = null
private styleElement: HTMLStyleElement | null = null
private renderFunction: (container: HTMLElement) => (() => void) | void
private unmountFunction: (() => void) | null = null
private options: ContentScriptOptions
constructor(renderFunction: (container: HTMLElement) => (() => void) | void, options: ContentScriptOptions = {}) {
this.renderFunction = renderFunction
this.options = {
rootElement: 'extension-root',
rootClassName: undefined, // React handles its own styling
stylesheets: ${JSON.stringify(cssImports.length > 0 ? cssImports : [
'./styles.css'
])},
...options
}
}
async mount(container?: HTMLElement): Promise<void> {
console.log('[Extension.js] React wrapper mount called')
if (this.rootElement) {
this.unmount()
}
// Create root element - React handles its own container styling
this.rootElement = container || document.createElement('div')
this.rootElement.id = this.options.rootElement!
if (this.options.rootClassName) {
this.rootElement.className = this.options.rootClassName
}
// React component handles its own styling
// Create shadow root for style isolation
this.shadowRoot = this.rootElement.attachShadow({ mode: 'open' })
// Create a host element inside the shadow root for rendering
const host = document.createElement('div')
this.shadowRoot.appendChild(host)
// Inject styles FIRST
console.log('[Extension.js] About to inject styles')
await this.injectStyles()
// Render React content
console.log('[Extension.js] About to render React content')
const result = this.renderFunction(host)
if (typeof result === 'function') {
this.unmountFunction = result
}
// Append to document if no container provided
if (!container) {
document.body.appendChild(this.rootElement)
}
console.log('[Extension.js] React wrapper mount complete')
}
unmount() {
if (this.unmountFunction) {
this.unmountFunction()
this.unmountFunction = null
}
if (this.rootElement && this.rootElement.parentNode) {
this.rootElement.parentNode.removeChild(this.rootElement)
}
this.rootElement = null
this.shadowRoot = null
this.styleElement = null
}
// Inject styles with hardcoded CSS content for React templates
private async injectStyles(): Promise<void> {
console.log('[Extension.js] React injectStyles called')
const targetRoot = this.shadowRoot || this.rootElement!
// Create style element
this.styleElement = document.createElement('style')
targetRoot.appendChild(this.styleElement)
// Fetch CSS content
try {
console.log('[Extension.js] About to fetch CSS')
const cssContent = await this.fetchCSS()
console.log('[Extension.js] CSS fetched, length:', cssContent.length)
this.styleElement.textContent = cssContent
console.log('[Extension.js] React CSS injected successfully')
} catch (error) {
console.error('[Extension.js] Failed to inject React CSS:', error)
}
// Setup HMR for CSS files
this.setupCSSHMR()
}
// Fetch CSS with hardcoded content for React templates
private async fetchCSS(): Promise<string> {
let allCSS = ''
console.log('[Extension.js] Processing React stylesheets:', this.options.stylesheets)
// CSS content map is injected at build time
const cssContentMap: Record<string, string> = ${JSON.stringify(cssContentMap)}
for (const stylesheet of this.options.stylesheets) {
try {
console.log('[Extension.js] Processing React stylesheet:', stylesheet)
// Check if we have hardcoded content for this stylesheet
if (cssContentMap[stylesheet]) {
const cssContent = cssContentMap[stylesheet]
allCSS += cssContent + '\n'
console.log('[Extension.js] Successfully injected React', stylesheet, 'content')
continue
}
// For stylesheets without hardcoded content, try to fetch them
const cssUrl = new URL(stylesheet, import.meta.url)
const response = await fetch(cssUrl)
const text = await response.text()
if (response.ok) {
allCSS += text + '\n'
console.log('[Extension.js] Successfully fetched stylesheet:', stylesheet)
} else {
console.warn('[Extension.js] Failed to fetch CSS:', stylesheet)
}
} catch (error) {
console.warn('[Extension.js] Failed to fetch React CSS:', stylesheet, error)
}
}
const result = allCSS || '/* No CSS loaded */'
console.log('[Extension.js] Final CSS result length:', result.length)
return result
}
// Setup CSS HMR for React templates
private setupCSSHMR() {
if (!import.meta.webpackHot) return
// Setup HMR for each CSS file
for (const stylesheet of this.options.stylesheets) {
import.meta.webpackHot?.accept(stylesheet, async () => {
try {
const cssContent = await this.fetchCSS()
if (this.styleElement) {
this.styleElement.textContent = cssContent
console.log('[Extension.js] React CSS updated via HMR:', stylesheet)
}
} catch (error) {
console.error('[Extension.js] Failed to update React CSS via HMR:', stylesheet, error)
}
})
}
}
}
// Initialize React content script with wrapper
function initializeReactContentScript(
options: ContentScriptOptions,
renderFunction: (container: HTMLElement) => (() => void) | void
): ContentScriptInstance {
const wrapper = new ReactContentScriptWrapper(renderFunction, options)
return {
mount: (container?: HTMLElement) => wrapper.mount(container),
unmount: () => wrapper.unmount()
}
}
// Auto-initialize the React content script with the wrapper
export function autoInitializeReactContentScript(
options: ContentScriptOptions = {}
): ContentScriptInstance {
// Get the render function from the imported contentScript
const renderFunction = contentScript(options)
// Initialize with the wrapper using the detected CSS imports
return initializeReactContentScript(options, renderFunction)
}
// Simple initialization for React
let unmount: (() => void) | undefined
async function initialize() {
console.log('[Extension.js] React wrapper initialize called')
if (unmount) {
console.log('[Extension.js] Unmounting previous React instance')
unmount()
}
// Get the render function from the contentScript function
const renderFunction = contentScript({})
const wrapper = new ReactContentScriptWrapper(renderFunction, {})
await wrapper.mount()
unmount = () => wrapper.unmount()
console.log('[Extension.js] React wrapper initialization complete')
}
if (import.meta.webpackHot) {
import.meta.webpackHot?.accept()
import.meta.webpackHot?.dispose(() => unmount?.())
// Accept changes to this file
import.meta.webpackHot?.accept(() => {
initialize()
})
}
if (document.readyState === 'complete') {
initialize()
} else {
document.addEventListener('readystatechange', () => {
if (document.readyState === 'complete') {
initialize()
}
})
}
export default ReactContentScriptWrapper
`;
return wrapperCode;
}
function vue_content_script_wrapper_extractCSSImports(source) {
const cssImports = [];
const lines = source.split('\n');
const cssImportPatterns = [
/^\s*import\s+['"]([^'"]*\.(?:css|scss|sass|less|module\.css))['"]/,
/^\s*import\s+['"]([^'"]*\.(?:css|scss|sass|less|module\.css))['"]\s*;?\s*$/,
/^\s*import\s+['"]([^'"]*\.(?:css|scss|sass|less|module\.css))['"]\s*from\s+['"][^'"]*['"]/
];
for (const line of lines){
const trimmedLine = line.trim();
if (!(trimmedLine.startsWith('//') || trimmedLine.startsWith('/*'))) for (const pattern of cssImportPatterns){
const match = pattern.exec(line);
if (match) {
const cssPath = match[1];
if (cssPath && !cssImports.includes(cssPath)) cssImports.push(cssPath);
}
}
}
return cssImports;
}
function generateVueWrapperCode(source, resourcePath) {
external_path_namespaceObject.basename(resourcePath, external_path_namespaceObject.extname(resourcePath));
const cssImports = vue_content_script_wrapper_extractCSSImports(source);
const resourceDir = external_path_namespaceObject.dirname(resourcePath);
if ('development' === process.env.EXTENSION_ENV) console.log("[Extension.js] Detected Vue framework with CSS imports:", cssImports);
const cssContentMap = {};
for (const cssImport of cssImports)try {
const cssPath = external_path_namespaceObject.resolve(resourceDir, cssImport);
if (external_fs_namespaceObject.existsSync(cssPath)) {
const cssContent = external_fs_namespaceObject.readFileSync(cssPath, 'utf-8');
const escapedCSS = cssContent.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\${/g, '\\${').replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
cssContentMap[cssImport] = escapedCSS;
if ('development' === process.env.EXTENSION_ENV) console.log(`[Extension.js] Read CSS content for ${cssImport}, length: ${cssContent.length}`);
} else if ('development' === process.env.EXTENSION_ENV) console.warn(`[Extension.js] CSS file not found: ${cssPath}`);
} catch (error) {
if ('development' === process.env.EXTENSION_ENV) console.warn(`[Extension.js] Failed to read CSS file ${cssImport}:`, error);
}
const wrapperCode = `
// Vue Content Script Wrapper - Auto-generated by Extension.js
// This wrapper provides Shadow DOM isolation and CSS injection for Vue content scripts
// Original Vue content script source (directive processed)
${source.replace(/'use shadow-dom'/g, "// 'use shadow-dom'").replace(/"use shadow-dom"/g, '// "use shadow-dom"')}
// Content script options interface
export interface ContentScriptOptions {
rootElement?: string
rootClassName?: string
stylesheets?: string[]
}
// Content script instance interface
export interface ContentScriptInstance {
mount: (container: HTMLElement) => void
unmount: () => void
}
// Vue Content script wrapper class
class VueContentScriptWrapper {
private rootElement: HTMLElement | null = null
private shadowRoot: ShadowRoot | null = null
private styleElement: HTMLStyleElement | null = null
private renderFunction: (container: HTMLElement) => (() => void) | void
private unmountFunction: (() => void) | null = null
private options: ContentScriptOptions
constructor(renderFunction: (container: HTMLElement) => (() => void) | void, options: ContentScriptOptions = {}) {
this.renderFunction = renderFunction
this.options = {
rootElement: 'extension-root',
rootClassName: undefined, // Vue handles its own styling
stylesheets: ${JSON.stringify(cssImports.length > 0 ? cssImports : [
'./styles.css'
])},
...options
}
}
async mount(container?: HTMLElement): Promise<void> {
console.log('[Extension.js] Vue wrapper mount called')
if (this.rootElement) {
this.unmount()
}
// Create root element - Vue handles its own container styling
this.rootElement = container || document.createElement('div')
this.rootElement.id = this.options.rootElement!
if (this.options.rootClassName) {
this.rootElement.className = this.options.rootClassName
}
// Vue component handles its own styling
// Create shadow root for style isolation
this.shadowRoot = this.rootElement.attachShadow({ mode: 'open' })
// Create a host element inside the shadow root for rendering
const host = document.createElement('div')
this.shadowRoot.appendChild(host)
// Inject styles FIRST
console.log('[Extension.js] About to inject styles')
await this.injectStyles()
// Render Vue content
console.log('[Extension.js] About to render Vue content')
const result = this.renderFunction(host)
if (typeof result === 'function') {
this.unmountFunction = result
}
// Append to document if no container provided
if (!container) {
document.body.appendChild(this.rootElement)
}
console.log('[Extension.js] Vue wrapper mount complete')
}
unmount() {
if (this.unmountFunction) {
this.unmountFunction()
this.unmountFunction = null
}
if (this.rootElement && this.rootElement.parentNode) {
this.rootElement.parentNode.removeChild(this.rootElement)
}
this.rootElement = null
this.shadowRoot = null
this.styleElement = null
}
// Inject styles with hardcoded CSS content for Vue templates
private async injectStyles(): Promise<void> {
console.log('[Extension.js] Vue injectStyles called')
const targetRoot = this.shadowRoot || this.rootElement!
// Create style element
this.styleElement = document.createElement('style')
targetRoot.appendChild(this.styleElement)
// Fetch CSS content
try {
console.log('[Extension.js] About to fetch CSS')
const cssContent = await this.fetchCSS()
console.log('[Extension.js] CSS fetched, length:', cssContent.length)
this.styleElement.textContent = cssContent
console.log('[Extension.js] Vue CSS injected successfully')
} catch (error) {
console.error('[Extension.js] Failed to inject Vue CSS:', error)
}
// Setup HMR for CSS files
this.setupCSSHMR()
}
// Fetch CSS with hardcoded content for Vue templates
private async fetchCSS(): Promise<string> {
let allCSS = ''
console.log('[Extension.js] Processing Vue stylesheets:', this.options.stylesheets)
// CSS content map is injected at build time
const cssContentMap: Record<string, string> = ${JSON.stringify(cssContentMap)}
for (const stylesheet of this.options.stylesheets) {
try {
console.log('[Extension.js] Processing Vue stylesheet:', stylesheet)
// Check if we have hardcoded content for this stylesheet
if (cssContentMap[stylesheet]) {
const cssContent = cssContentMap[stylesheet]
allCSS += cssContent + '\n'
console.log('[Extension.js] Successfully injected Vue', stylesheet, 'content')
continue
}
// For stylesheets without hardcoded content, try to fetch them
const cssUrl = new URL(stylesheet, import.meta.url)
const response = await fetch(cssUrl)
const text = await response.text()
if (response.ok) {
allCSS += text + '\n'
console.log('[Extension.js] Successfully fetched stylesheet:', stylesheet)
} else {
console.warn('[Extension.js] Failed to fetch CSS:', stylesheet)
}
} catch (error) {
console.warn('[Extension.js] Failed to fetch Vue CSS:', stylesheet, error)
}
}
return allCSS
}
// Setup CSS HMR for Vue templates
private setupCSSHMR() {
if (!import.meta.webpackHot) return
// Setup HMR for each CSS file
for (const stylesheet of this.options.stylesheets) {
import.meta.webpackHot?.accept(stylesheet, async () => {
try {
const cssContent = await this.fetchCSS()
if (this.styleElement) {
this.styleElement.textContent = cssContent
console.log('[Extension.js] Vue CSS updated via HMR:', stylesheet)
}
} catch (error) {
console.error('[Extension.js] Failed to update Vue CSS via HMR:', stylesheet, error)
}
})
}
}
}
// Initialize Vue content script with wrapper
function initializeVueContentScript(
options: ContentScriptOptions,
renderFunction: (container: HTMLElement) => (() => void) | void
): ContentScriptInstance {
const wrapper = new VueContentScriptWrapper(renderFunction, options)
return {
mount: (container?: HTMLElement) => wrapper.mount(container),
unmount: () => wrapper.unmount()
}
}
// Auto-initialize the Vue content script with the wrapper
export function autoInitializeVueContentScript(
options: ContentScriptOptions = {}
): ContentScriptInstance {
// Get the render function from the imported contentScript
const renderFunction = contentScript(options)
// Initialize with the wrapper using the detected CSS imports
return initializeVueContentScript(options, renderFunction)
}
// Simple initialization for Vue
let unmount: (() => void) | undefined
async function initialize() {
console.log('[Extension.js] Vue wrapper initialize called')
if (unmount) {
console.log('[Extension.js] Unmounting previous Vue instance')
unmount()
}
// Get the render function from the contentScript function
const renderFunction = contentScript({})
const wrapper = new VueContentScriptWrapper(renderFunction, {})
await wrapper.mount()
unmount = () => wrapper.unmount()
console.log('[Extension.js] Vue wrapper initialization complete')
}
if (import.meta.webpackHot) {
import.meta.webpackHot?.accept()
import.meta.webpackHot?.dispose(() => unmount?.())
// Accept changes to this file
import.meta.webpackHot?.accept(() => {
initialize()
})
}
if (document.readyState === 'complete') {
initialize()
} else {
document.addEventListener('readystatechange', () => {
if (document.readyState === 'complete') {
initialize()
}
})
}
export default VueContentScriptWrapper
`;
return wrapperCode;
}
function svelte_content_script_wrapper_extractCSSImports(source) {
const cssImports = [];
const lines = source.split('\n');
const cssImportPatterns = [
/^\s*import\s+['"]([^'"]*\.(?:css|scss|sass|less|module\.css))['"]/,
/^\s*import\s+['"]([^'"]*\.(?:css|scss|sass|less|module\.css))['"]\s*;?\s*$/,
/^\s*import\s+['"]([^'"]*\.(?:css|scss|sass|less|module\.css))['"]\s*from\s+['"][^'"]*['"]/
];
for (const line of lines){
const trimmedLine = line.trim();
if (!(trimmedLine.startsWith('//') || trimmedLine.startsWith('/*'))) for (const pattern of cssImportPatterns){
const match = pattern.exec(line);
if (match) {
const cssPath = match[1];
if (cssPath && !cssImports.includes(cssPath)) cssImports.push(cssPath);
}
}
}
return cssImports;
}
function generateSvelteWrapperCode(source, resourcePath) {
external_path_namespaceObject.basename(resourcePath, external_path_namespaceObject.extname(resourcePath));
const cssImports = svelte_content_script_wrapper_extractCSSImports(source);
const resourceDir = external_path_namespaceObject.dirname(resourcePath);
if ('development' === process.env.EXTENSION_ENV) console.log("[Extension.js] Detected Svelte framework with CSS imports:", cssImports);
const cssContentMap = {};
for (const cssImport of cssImports)try {
const cssPath = external_path_namespaceObject.resolve(resourceDir, cssImport);
if (external_fs_namespaceObject.existsSync(cssPath)) {
const cssContent = external_fs_namespaceObject.readFileSync(cssPath, 'utf-8');
const escapedCSS = cssContent.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\${/g, '\\${').replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
cssContentMap[cssImport] = escapedCSS;
if ('development' === process.env.EXTENSION_ENV) console.log(`[Extension.js] Read CSS content for ${cssImport}, length: ${cssContent.length}`);
} else if ('development' === process.env.EXTENSION_ENV) console.warn(`[Extension.js] CSS file not found: ${cssPath}`);
} catch (error) {
if ('development' === process.env.EXTENSION_ENV) console.warn(`[Extension.js] Failed to read CSS file ${cssImport}:`, error);
}
const wrapperCode = `
// Svelte Content Script Wrapper - Auto-generated by Extension.js
// This wrapper provides Shadow DOM isolation and CSS injection for Svelte content scripts
// Original Svelte content script source (directive processed)
${source.replace(/'use shadow-dom'/g, "// 'use shadow-dom'").replace(/"use shadow-dom"/g, '// "use shadow-dom"')}
// Content script options interface
export interface ContentScriptOptions {
rootElement?: string
rootClassName?: string
stylesheets?: string[]
}
// Content script instance interface
export interface ContentScriptInstance {
mount: (container: HTMLElement) => void
unmount: () => void
}
// Svelte Content script wrapper class
class SvelteContentScriptWrapper {
private rootElement: HTMLElement | null = null
private shadowRoot: ShadowRoot | null = null
private styleElement: HTMLStyleElement | null = null
private renderFunction: (container: HTMLElement) => (() => void) | void
private unmountFunction: (() => void) | null = null
private options: ContentScriptOptions
constructor(renderFunction: (container: HTMLElement) => (() => void) | void, options: ContentScriptOptions = {}) {
this.renderFunction = renderFunction
this.options = {
rootElement: 'extension-root',
rootClassName: undefined, // Svelte handles its own styling
stylesheets: ${JSON.stringify(cssImports.length > 0 ? cssImports : [
'./styles.css'
])},
...options
}
}
async mount(container?: HTMLElement): Promise<void> {
console.log('[Extension.js] Svelte wrapper mount called')
if (this.rootElement) {
this.unmount()
}
// Create root element - Svelte handles its own container styling
this.rootElement = container || document.createElement('div')
this.rootElement.id = this.options.rootElement!
if (this.options.rootClassName) {
this.rootElement.className = this.options.rootClassName
}
// Svelte component handles its own styling
// Create shadow root for style isolation
this.shadowRoot = this.rootElement.attachShadow({ mode: 'open' })
// Create a host element inside the shadow root for rendering
const host = document.createElement('div')
this.shadowRoot.appendChild(host)
// Inject styles FIRST
console.log('[Extension.js] About to inject styles')
await this.injectStyles()
// Render Svelte content
console.log('[Extension.js] About to render Svelte content')
const result = this.renderFunction(host)
if (typeof result === 'function') {
this.unmountFunction = result
}
// Append to document if no container provided
if (!container) {
document.body.appendChild(this.rootElement)
}
console.log('[Extension.js] Svelte wrapper mount complete')
}
unmount() {
if (this.unmountFunction) {
this.unmountFunction()
this.unmountFunction = null
}
if (this.rootElement && this.rootElement.parentNode) {
this.rootElement.parentNode.removeChild(this.rootElement)
}
this.rootElement = null
this.shadowRoot = null
this.styleElement = null
}
// Inject styles with hardcoded CSS content for Svelte templates
private async injectStyles(): Promise<void> {
console.log('[Extension.js] Svelte injectStyles called')
const targetRoot = this.shadowRoot || this.rootElement!
// Create style element
this.styleElement = document.createElement('style')
targetRoot.appendChild(this.styleElement)
// Fetch CSS content
try {
console.log('[Extension.js] About to fetch CSS')
const cssContent = await this.fetchCSS()
console.log('[Extension.js] CSS fetched, length:', cssContent.length)
this.styleElement.textContent = cssContent
console.log('[Extension.js] Svelte CSS injected successfully')
} catch (error) {
console.error('[Extension.js] Failed to inject Svelte CSS:', error)
}
// Setup HMR for CSS files
this.setupCSSHMR()
}
// Fetch CSS with hardcoded content for Svelte templates
private async fetchCSS(): Promise<string> {
let allCSS = ''
console.log('[Extension.js] Processing Svelte stylesheets:', this.options.stylesheets)
// CSS content map is injected at build time
const cssContentMap: Record<string, string> = ${JSON.stringify(cssContentMap)}
for (const stylesheet of this.options.stylesheets) {
try {
console.log('[Extension.js] Processing Svelte stylesheet:', stylesheet)
// Check if we have hardcoded content for this stylesheet
if (cssContentMap[stylesheet]) {
const cssContent = cssContentMap[stylesheet]
allCSS += cssContent + '\n'
console.log('[Extension.js] Successfully injected Svelte', stylesheet, 'content')
continue
}
// For stylesheets without hardcoded content, try to fetch them
const cssUrl = new URL(stylesheet, import.meta.url)
const response = await fetch(cssUrl)
const text = await response.text()
if (response.ok) {
allCSS += text + '\n'
console.log('[Extension.js] Successfully fetched stylesheet:', stylesheet)
} else {
console.warn('[Extension.js] Failed to fetch CSS:', stylesheet)
}
} catch (error) {
console.warn('[Extension.js] Failed to fetch Svelte CSS:', stylesheet, error)
}
}
return allCSS
}
// Setup CSS HMR for Svelte templates
private setupCSSHMR() {
if (!import.meta.webpackHot) return
// Setup HMR for each CSS file
for (const stylesheet of this.options.stylesheets) {
import.meta.webpackHot?.accept(stylesheet, async () => {
try {
const cssContent = await this.fetchCSS()
if (this.styleElement) {
this.styleElement.textContent = cssContent
console.log('[Extension.js] Svelte CSS updated via HMR:', stylesheet)
}
} catch (error) {
console.error('[Extension.js] Failed to update Svelte CSS via HMR:', stylesheet, error)
}
})
}
}
}
// Initialize Svelte content script with wrapper
function initializeSvelteContentScript(
options: ContentScriptOptions,
renderFunction: (container: HTMLElement) => (() => void) | void
): ContentScriptInstance {
const wrapper = new SvelteContentScriptWrapper(renderFunction, options)
return {
mount: (container?: HTMLElement) => wrapper.mount(container),
unmount: () => wrapper.unmount()
}
}
// Auto-initialize the Svelte content script with the wrapper
export function autoInitializeSvelteContentScript(
options: ContentScriptOptions = {}
): ContentScriptInstance {
// Get the render function from the imported contentScript
const renderFunction = contentScript(options)
// Initialize with the wrapper using the detected CSS imports
return initializeSvelteContentScript(options, renderFunction)
}
// Simple initialization for Svelte
let unmount: (() => void) | undefined
async function initialize() {
console.log('[Extension.js] Svelte wrapper initialize called')
if (unmount) {
console.log('[Extension.js] Unmounting previous Svelte instance')
unmount()
}
// Get the render function from the contentScript function
const renderFunction = contentScript({})
const wrapper = new SvelteContentScriptWrapper(renderFunction, {})
await wrapper.mount()
unmount = () => wrapper.unmount()
console.log('[Extension.js] Svelte wrapper initialization complete')
}
if (import.meta.webpackHot) {
import.meta.webpackHot?.accept()
import.meta.webpackHot?.dispose(() => unmount?.())
// Accept changes to this file
import.meta.webpackHot?.accept(() => {
initialize()
})
}
if (document.readyState === 'complete') {
initialize()
} else {
document.addEventListener('readystatechange', () => {
if (document.readyState === 'complete') {
initialize()
}
})
}
export default SvelteContentScriptWrapper
`;
return wrapperCode;
}
function preact_content_script_wrapper_extractCSSImports(source) {
const cssImports = [];
const lines = source.split('\n');
const cssImportPatterns = [
/^\s*import\s+['"]([^'"]*\.(?:css|scss|sass|less|module\.css))['"]/,
/^\s*import\s+['"]([^'"]*\.(?:css|scss|sass|less|module\.css))['"]\s*;?\s*$/,
/^\s*import\s+['"]([^'"]*\.(?:css|scss|sass|less|module\.css))['"]\s*from\s+['"][^'"]*['"]/
];
for (const line of lines){
const trimmedLine = line.trim();
if (!(trimmedLine.startsWith('//') || trimmedLine.startsWith('/*'))) for (const pattern of cssImportPatterns){
const match = pattern.exec(line);
if (match) {
const cssPath = match[1];
if (cssPath && !cssImports.includes(cssPath)) cssImports.push(cssPath);
}
}
}
return cssImports;
}
function generatePreactWrapperCode(source, resourcePath) {
external_path_namespaceObject.basename(resourcePath, external_path_namespaceObject.extname(resourcePath));
const cssImports = preact_content_script_wrapper_extractCSSImports(source);
const resourceDir = external_path_namespaceObject.dirname(resourcePath);
if ('development' === process.env.EXTENSION_ENV) console.log("[Extension.js] Detected Preact framework with CSS imports:", cssImports);
const cssContentMap = {};
for (const cssImport of cssImports)try {
const cssPath = external_path_namespaceObject.resolve(resourceDir, cssImport);
if (external_fs_namespaceObject.existsSync(cssPath)) {
const cssContent = external_fs_namespaceObject.readFileSync(cssPath, 'utf-8');
const escapedCSS = cssContent.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\${/g, '\\${').replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
cssContentMap[cssImport] = escapedCSS;
if ('development' === process.env.EXTENSION_ENV) console.log(`[Extension.js] Read CSS content for ${cssImport}, length: ${cssContent.length}`);
} else if ('development' === process.env.EXTENSION_ENV) console.warn(`[Extension.js] CSS file not found: ${cssPath}`);
} catch (error) {
if ('development' === process.env.EXTENSION_ENV) console.warn(`[Extension.js] Failed to read CSS file ${cssImport}:`, error);
}
const wrapperCode = `
// Preact Content Script Wrapper - Auto-generated by Extension.js
// This wrapper provides Shadow DOM isolation and CSS injection for Preact content scripts
// Original Preact content script source (directive processed)
${source.replace(/'use shadow-dom'/g, "// 'use shadow-dom'").replace(/"use shadow-dom"/g, '// "use shadow-dom"')}
// Content script options interface
export interface ContentScriptOptions {
rootElement?: string
rootClassName?: string
stylesheets?: string[]
}
// Content script instance interface
export interface ContentScriptInstance {
mount: (container: HTMLElement) => void
unmount: () => void
}
// Preact Content script wrapper class
class PreactContentScriptWrapper {
private rootElement: HTMLElement | null = null
private shadowRoot: ShadowRoot | null = null
private styleElement: HTMLStyleElement | null = null
private renderFunction: (container: HTMLElement) => (() => void) | void
private unmountFunction: (() => void) | null = null
private options: ContentScriptOptions
constructor(renderFunction: (container: HTMLElement) => (() => void) | void, options: ContentScriptOptions = {}) {
this.renderFunction = renderFunction
this.options = {
rootElement: 'extension-root',
rootClassName: undefined, // Preact handles its own styling
stylesheets: ${JSON.stringify(cssImports.length > 0 ? cssImports : [
'./styles.css'
])},
...options
}
}
async mount(container?: HTMLElement): Promise<void> {
console.log('[Extension.js] Preact wrapper mount called')
if (this.rootElement) {
this.unmount()
}
// Create root element - Preact handles its own container styling
this.rootElement = container || document.createElement('div')
this.rootElement.id = this.options.rootElement!
if (this.options.rootClassName) {
this.rootElement.className = this.options.rootClassName
}
// Preact component handles its own styling
// Create shadow root for style isolation
this.shadowRoot = this.rootElement.attachShadow({ mode: 'open' })
// Inject styles FIRST
console.log('[Extension.js] About to inject styles')
await this.injectStyles()
// Render Preact content
console.log('[Extension.js] About to render Preact content')
const result = this.renderFunction(this.shadowRoot as any)
if (typeof result === 'function') {
this.unmountFunction = result
}
// Append to document if no container provided
if (!container) {
document.body.appendChild(this.rootElement)
}
console.log('[Extension.js] Preact wrapper mount complete')
}
unmount() {
if (this.unmountFunction) {
this.unmountFunction()
this.unmountFunction = null
}
if (this.rootElement && this.rootElement.parentNode) {
this.rootElement.parentNode.removeChild(this.rootElement)
}
this.rootElement = null
this.shadowRoot = null
this.styleElement = null
}
// Inject styles with hardcoded CSS content for Preact templates
private async injectStyles(): Promise<void> {
console.log('[Extension.js] Preact injectStyles called')
const targetRoot = this.shadowRoot || this.rootElement!
// Create style element
this.styleElement = document.createElement('style')
targetRoot.appendChild(this.styleElement)
// Fetch CSS content
try {
console.log('[Extension.js] About to fetch CSS')
const cssContent = await this.fetchCSS()
console.log('[Extension.js] CSS fetched, length:', cssContent.length)
this.styleElement.textContent = cssContent
console.log('[Extension.js] Preact CSS injected successfully')
} catch (error) {
console.error('[Extension.js] Failed to inject Preact CSS:', error)
}
// Setup HMR for CSS files
this.setupCSSHMR()
}
// Fetch CSS with hardcoded content for Preact templates
private async fetchCSS(): Promise<string> {
let allCSS = ''
console.log('[Extension.js] Processing Preact stylesheets:', this.options.stylesheets)
// CSS content map is injected at build time
const cssContentMap: Record<string, string> = ${JSON.stringify(cssContentMap)}
for (const stylesheet of this.options.stylesheets) {
try {
console.log('[Extension.js] Processing Preact stylesheet:', stylesheet)
// Check if we have hardcoded content for this stylesheet
if (cssContentMap[stylesheet]) {
const cssContent = cssContentMap[stylesheet]
allCSS += cssContent + '\\n'
console.log(\`[Extension.js] Successfully injected Preact \${stylesheet} content\`)
continue
}
// For stylesheets without hardcoded content, try to fetch them
const cssUrl = new URL(stylesheet, import.meta.url)
const response = await fetch(cssUrl)
const text = await response.text()
if (response.ok) {
allCSS += text + '\\n'
console.log('[Extension.js] Successfully fetched stylesheet:', stylesheet)
} else {
console.warn('[Extension.js] Failed to fetch CSS:', stylesheet)
}
} catch (error) {
console.warn('[Extension.js] Failed to fetch Preact CSS:', stylesheet, error)
}
}
return allCSS
}
// Setup CSS HMR for Preact templates
private setupCSSHMR() {
if (!import.meta.webpackHot) return
// Setup HMR for each CSS file
for (const stylesheet of this.options.stylesheets) {
import.meta.webpackHot?.accept(stylesheet, async () => {
try {
const cssContent = await this.fetchCSS()
if (this.styleElement) {
this.styleElement.textContent = cssContent
console.log('[Extension.js] Preact CSS updated via HMR:', stylesheet)
}
} catch (error) {
console.error('[Extension.js] Failed to update Preact CSS via HMR:', stylesheet, error)
}
})
}
}
}
// Initialize Preact content script with wrapper
function initializePreactContentScript(
options: ContentScriptOptions,
renderFunction: (container: HTMLElement) => (() => void) | void
): ContentScriptInstance {
const wrapper = new PreactContentScriptWrapper(renderFunction, options)
return {
mount: (container?: HTMLElement) => wrapper.mount(container),
unmount: () => wrapper.unmount()
}
}
// Auto-initialize the Preact content script with the wrapper
export function autoInitializePreactContentScript(
options: ContentScriptOptions = {}
): ContentScriptInstance {
// Get the render function from the imported contentScript
const renderFunction = contentScript(options)
// Initialize with the wrapper using the detected CSS imports
return initializePreactContentScript(options, renderFunction)
}
// Simple initialization for Preact
let unmount: (() => void) | undefined
async function initialize() {
console.log('[Extension.js] Preact wrapper initialize called')
if (unmount) {
console.log('[Extension.js] Unmounting previous Preact instance')
unmount()
}
// Get the render function from the contentScript function
const renderFunction = contentScript({})
const wrapper = new PreactContentScriptWrapper(renderFunction, {})
await wrapper.mount()
unmount = () => wrapper.unmount()
console.log('[Extension.js] Preact wrapper initialization complete')
}
if (import.meta.webpackHot) {
import.meta.webpackHot?.accept()
import.meta.webpackHot?.dispose(() => unmount?.())
// Accept changes to this file
import.meta.webpackHot?.accept(() => {
initialize()
})
}
if (document.readyState === 'complete') {
initialize()
} else {
document.addEventListener('readystatechange', () => {
if (document.readyState === 'complete') {
initialize()
}
})
}
export default PreactContentScriptWrapper
`;
return wrapperCode;
}
function typescript_content_script_wrapper_extractCSSImports(source) {
const cssImports = [];
const lines = source.split('\n');
const cssImportPatterns = [
/^\s*import\s+['"]([^'"]*\.(?:css|scss|sass|less|module\.css))['"]/,
/^\s*import\s+['"]([^'"]*\.(?:css|scss|sass|less|module\.css))['"]\s*;?\s*$/,
/^\s*import\s+['"]([^'"]*\.(?:css|scss|sass|less|module\.css))['"]\s*from\s+['"][^'"]*['"]/
];
for (const line of lines){
const trimmedLine = line.trim();
if (!(trimmedLine.startsWith('//') || trimmedLine.startsWith('/*'))) for (const pattern of cssImportPatterns){
const match = pattern.exec(line);
if (match) {
const cssPath = match[1];
if (cssPath && !cssImports.includes(cssPath)) cssImports.push(cssPath);
}
}
}
return cssImports;
}
function generateTypeScriptWrapperCode(source, resourcePath) {
external_path_namespaceObject.basename(resourcePath, external_path_namespaceObject.extname(resourcePath));
const cssImports = typescript_content_script_wrapper_extractCSSImports(source);
const resourceDir = external_path_namespaceObject.dirname(resourcePath);
if ('development' === process.env.EXTENSION_ENV) console.log("[Extension.js] Detected TypeScript framework with CSS imports:", cssImports);
const cssContentMap = {};
for (const cssImport of cssImports)try {
const cssPath = external_path_namespaceObject.resolve(resourceDir, cssImport);
if (external_fs_namespaceObject.existsSync(cssPath)) {
const cssContent = external_fs_namespaceObject.readFileSync(cssPath, 'utf-8');
const escapedCSS = cssContent.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\${/g, '\\${').replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
cssContentMap[cssImport] = escapedCSS;
if ('development' === process.env.EXTENSION_ENV) console.log(`[Extension.js] Read CSS content for ${cssImport}, length: ${cssContent.length}`);
} else if ('development' === process.env.EXTENSION_ENV) console.warn(`[Extension.js] CSS file not found: ${cssPath}`);
} catch (error) {
if ('development' === process.env.EXTENSION_ENV) console.warn(`[Extension.js] Failed to read CSS file ${cssImport}:`, error);
}
const wrapperCode = `
// TypeScript Content Script Wrapper - Auto-generated by Extension.js
// This wrapper provides Shadow DOM isolation and CSS injection for TypeScript content scripts
// Original TypeScript content script source (directive processed)
${source.replace(/'use shadow-dom'/g, "// 'use shadow-dom'").replace(/"use shadow-dom"/g, '// "use shadow-dom"')}
// Content script options interface
export interface ContentScriptOptions {
rootElement?: string
rootClassName?: string
stylesheets?: string[]
}
// Content script instance interface
export interface ContentScriptInstance {
mount: (container: HTMLElement) => void
unmount: () => void
}
// TypeScript Content script wrapper class
class TypeScriptContentScriptWrapper {
private rootElement: HTMLElement | null = null
private shadowRoot: ShadowRoot | null = null
private styleElement: HTMLStyleElement | null = null
private renderFunction: (container: HTMLElement) => (() => void) | void
private unmountFunction: (() => void) | null = null
private options: ContentScriptOptions
constructor(renderFunction: (container: HTMLElement) => (() => void) | void, options: ContentScriptOptions = {}) {
this.renderFunction = renderFunction
this.options = {
rootElement: 'extension-root',
rootClassName: undefined, // TypeScript handles its own styling
stylesheets: ${JSON.stringify(cssImports)},
...options
}
}
async mount(container?: HTMLElement): Promise<void> {
console.log('[Extension.js] TypeScript wrapper mount called')
if (this.rootElement) {
this.unmount()
}
// Create root element - TypeScript handles its own container styling
this.rootElement = container || document.createElement('div')
this.rootElement.id = this.options.rootElement!
if (this.options.rootClassName) {
this.rootElement.className = this.options.rootClassName
}
// Create shadow root for style isolation
this.shadowRoot = this.rootElement.attachShadow({ mode: 'open' })
// Inject styles FIRST
console.log('[Extension.js] About to inject styles')
await this.injectStyles()
// Render TypeScript content
console.log('[Extension.js] About to render TypeScript content')
const result = this.renderFunction(this.shadowRoot as any)
if (typeof result === 'function') {
this.unmountFunction = result
}
// Append to document if no container provided
if (!container) {
document.body.appendChild(this.rootElement)
}
console.log('[Extension.js] TypeScript wrapper mount complete')
}
unmount() {
if (this.unmountFunction) {
this.unmountFunction()
this.unmountFunction = null
}
if (this.rootElement && this.rootElement.parentNode) {
this.rootElement.parentNode.removeChild(this.rootElement)
}
this.rootElement = null
this.shadowRoot = null
this.styleElement = null
}
// Inject styles with hardcoded CSS content for TypeScript templates
private async injectStyles(): Promise<void> {
console.log('[Extension.js] TypeScript injectStyles called')
const targetRoot = this.shadowRoot || this.rootElement!
// Create style element
this.styleElement = document.createElement('style')
targetRoot.appendChild(this.styleElement)
// Fetch CSS content
try {
console.log('[Extension.js] About to fetch CSS')
const cssContent = await this.fetchCSS()
console.log('[Extension.js] CSS fetched, length:', cssContent.length)
this.styleElement.textContent = cssContent
console.log('[Extension.js] TypeScript CSS injected successfully')
} catch (error) {
console.error('[Extension.js] Failed to inject TypeScript CSS:', error)
}
// Setup HMR for CSS files
this.setupCSSHMR()
}
// Fetch CSS with hardcoded content for TypeScript templates
private async fetchCSS(): Promise<string> {
let allCSS = ''
console.log('[Extension.js] Processing TypeScript stylesheets:', this.options.stylesheets)
// CSS content map is injected at build time
const cssContentMap: Record<string, string> = ${JSON.stringify(cssContentMap)}
for (const stylesheet of this.options.stylesheets) {
try {
console.log('[Extension.js] Processing TypeScript stylesheet:', stylesheet)
// Check if we have hardcoded content for this stylesheet
if (cssContentMap[stylesheet]) {
const cssContent = cssContentMap[stylesheet]
allCSS += cssContent + '\\n'
console.log(\`[Extension.js] Successfully injected TypeScript \${stylesheet} content\`)
continue
}
// For stylesheets without hardcoded content, try to fetch them
const cssUrl = new URL(stylesheet, import.meta.url)
const response = await fetch(cssUrl)
const text = await response.text()
if (response.ok) {
allCSS += text + '\\n'
console.log('[Extension.js] Successfully fetched stylesheet:', stylesheet)
} else {
console.warn('[Extension.js] Failed to fetch CSS:', stylesheet)
}
} catch (error) {
console.warn('[Extension.js] Failed to fetch TypeScript CSS:', stylesheet, error)
}
}
return allCSS
}
// Setup CSS HMR for TypeScript templ