-
-
Notifications
You must be signed in to change notification settings - Fork 104
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Port webpack-target-webextension to Rspack
- Loading branch information
1 parent
9b2e141
commit 9132320
Showing
15 changed files
with
862 additions
and
963 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
...-reload-strategy/target-web-extension-plugin/rspack-target-webextension/BrowserRuntime.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// @ts-check | ||
const basic = [ | ||
`var isBrowser = !!(() => { try { return browser.runtime.getURL("/") } catch(e) {} })()`, | ||
`var isChrome = !!(() => { try { return chrome.runtime.getURL("/") } catch(e) {} })()` | ||
] | ||
const strong = [ | ||
...basic, | ||
`var runtime = isBrowser ? browser : isChrome ? chrome : { get runtime() { throw new Error("No chrome or browser runtime found") } }` | ||
] | ||
const weak = [ | ||
...basic, | ||
`var runtime = isBrowser ? browser : isChrome ? chrome : (typeof self === 'object' && self.addEventListener) ? { get runtime() { throw new Error("No chrome or browser runtime found") } } : { runtime: { getURL: x => x } }` | ||
] | ||
|
||
export default (getWeak = false) => (getWeak ? weak : strong) |
102 changes: 102 additions & 0 deletions
102
...tup-reload-strategy/target-web-extension-plugin/rspack-target-webextension/ChunkLoader.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import {Compiler} from '@rspack/core' | ||
|
||
import {LoadScriptRuntimeModule} from './RuntimeModules/LoadScript' | ||
import {PublicPathRuntimeModule} from './RuntimeModules/PublicPath' | ||
import {AutoPublicPathRuntimeModule} from './RuntimeModules/AutoPublicPath' | ||
import {ChunkLoaderFallbackRuntimeModule} from './RuntimeModules/ChunkLoaderFallback' | ||
import {BackgroundOptions} from './types' | ||
|
||
export class ChuckLoaderRuntimePlugin { | ||
private readonly options: BackgroundOptions | ||
private readonly weakRuntimeCheck: boolean | ||
|
||
constructor(options: BackgroundOptions, weakRuntimeCheck: boolean) { | ||
this.options = options | ||
this.weakRuntimeCheck = weakRuntimeCheck | ||
} | ||
|
||
apply(compiler: Compiler) { | ||
const {RuntimeGlobals} = compiler.webpack | ||
const {options} = this | ||
|
||
compiler.hooks.compilation.tap( | ||
ChuckLoaderRuntimePlugin.name, | ||
(compilation) => { | ||
compilation.hooks.runtimeRequirementInTree | ||
.for(RuntimeGlobals.loadScript) | ||
.tap(ChuckLoaderRuntimePlugin.name, (chunk) => { | ||
compilation.addRuntimeModule( | ||
chunk, | ||
LoadScriptRuntimeModule( | ||
compiler.webpack, | ||
compilation.outputOptions.environment && | ||
compilation.outputOptions.environment.dynamicImport, | ||
options && options.classicLoader !== false, | ||
this.weakRuntimeCheck | ||
) | ||
) | ||
return true | ||
}) | ||
|
||
compilation.hooks.runtimeRequirementInTree | ||
.for(RuntimeGlobals.publicPath) | ||
.tap(ChuckLoaderRuntimePlugin.name, (chunk, set) => { | ||
const {outputOptions} = compilation | ||
const {publicPath, scriptType} = outputOptions | ||
|
||
if (publicPath === 'auto') { | ||
const module = AutoPublicPathRuntimeModule( | ||
compiler.webpack, | ||
this.weakRuntimeCheck | ||
) | ||
|
||
if (scriptType !== 'module') { | ||
set.add(RuntimeGlobals.global) | ||
} | ||
|
||
compilation.addRuntimeModule(chunk, module) | ||
} else { | ||
const module = PublicPathRuntimeModule( | ||
compiler.webpack, | ||
this.weakRuntimeCheck | ||
) | ||
|
||
if ( | ||
typeof publicPath !== 'string' || | ||
/\[(full)?hash\]/.test(publicPath) | ||
) { | ||
module.fullHash = true | ||
} | ||
|
||
compilation.addRuntimeModule(chunk, module) | ||
} | ||
return true | ||
}) | ||
|
||
if (options && options.classicLoader !== false) { | ||
compilation.hooks.afterChunks.tap( | ||
ChuckLoaderRuntimePlugin.name, | ||
() => { | ||
const {entry, pageEntry, serviceWorkerEntry} = options | ||
const entryPoint = entry && compilation.entrypoints.get(entry) | ||
const entryPoint2 = | ||
pageEntry && compilation.entrypoints.get(pageEntry) | ||
const entryPoint3 = | ||
serviceWorkerEntry && | ||
compilation.entrypoints.get(serviceWorkerEntry) | ||
|
||
for (const entry of [entryPoint, entryPoint2, entryPoint3]) { | ||
if (!entry) continue | ||
|
||
compilation.addRuntimeModule( | ||
entry.chunks[0], | ||
ChunkLoaderFallbackRuntimeModule(compiler.webpack) | ||
) | ||
} | ||
} | ||
) | ||
} | ||
} | ||
) | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
...up-reload-strategy/target-web-extension-plugin/rspack-target-webextension/HMRDevServer.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import {Compiler} from '@rspack/core' | ||
|
||
// @ts-check | ||
export class HMRDevServerPlugin { | ||
apply(compiler: Compiler) { | ||
if (!compiler.options.devServer) compiler.options.devServer = {} | ||
const devServer = compiler.options.devServer | ||
|
||
setDefault(devServer, 'devMiddleware', {}) | ||
// Extensions cannot be loaded over network | ||
setDefault(devServer.devMiddleware, 'writeToDisk', true) | ||
|
||
if (!devServer.hot) return | ||
|
||
setDefault(devServer, 'host', '127.0.0.1') | ||
setDefault(devServer, 'client', { | ||
overlay: false, | ||
progress: false, | ||
webSocketURL: { | ||
protocol: 'ws' | ||
} | ||
}) | ||
// Overlay doesn't work well in content script. | ||
setDefault(devServer.client, 'overlay', false) | ||
// Progress is annoying in console. | ||
setDefault(devServer.client, 'progress', false) | ||
// In content script loaded in https:// pages, it will try to use wss:// because of protocol detect. | ||
setDefault(devServer.client, 'webSocketURL', {protocol: 'ws'}) | ||
setDefault(devServer.client.webSocketURL, 'protocol', 'ws') | ||
|
||
// HMR requires CORS requests in content scripts. | ||
setDefault(devServer, 'allowedHosts', 'all') | ||
setDefault(devServer, 'headers', { | ||
'Access-Control-Allow-Origin': '*' | ||
}) | ||
|
||
// Avoid listening to node_modules | ||
setDefault(devServer, 'static', {watch: {ignored: /\bnode_modules\b/}}) | ||
setDefault(devServer.static, 'watch', {ignored: /\bnode_modules\b/}) | ||
isObject(devServer.static) && | ||
setDefault(devServer.static.watch, 'ignored', /\bnode_modules\b/) | ||
} | ||
} | ||
|
||
function setDefault(obj: {[x: string]: any}, key: string | number, val: any) { | ||
if (isObject(obj) && obj[key] === undefined) obj[key] = val | ||
} | ||
|
||
function isObject(x: any): x is Record<string, any> { | ||
return typeof x === 'object' && x !== null | ||
} |
60 changes: 60 additions & 0 deletions
60
...oad-strategy/target-web-extension-plugin/rspack-target-webextension/NoDangerNamePlugin.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import {Compiler} from '@rspack/core' | ||
|
||
// @ts-check | ||
export class NoDangerNamePlugin { | ||
apply(compiler: Compiler) { | ||
const Error = compiler.webpack.WebpackError | ||
|
||
// Chrome bug https://bugs.chromium.org/p/chromium/issues/detail?id=1108199 | ||
{ | ||
const optimization = compiler.options.optimization | ||
|
||
if (optimization.splitChunks === undefined) | ||
optimization.splitChunks = {automaticNameDelimiter: '-'} | ||
else if ( | ||
optimization.splitChunks && | ||
optimization.splitChunks.automaticNameDelimiter === undefined | ||
) { | ||
optimization.splitChunks.automaticNameDelimiter = '-' | ||
} | ||
} | ||
|
||
compiler.hooks.emit.tap(NoDangerNamePlugin.name, (compilation) => { | ||
const with_ = [] | ||
const withTilde = [] | ||
for (const file in compilation.assets) { | ||
if (file.startsWith('_')) { | ||
if (file.startsWith('_locales/') || file === '_locales') { | ||
} else with_.push(String(file)) | ||
} | ||
if (file.includes('~')) withTilde.push(String(file)) | ||
} | ||
if (with_.length) { | ||
compilation.errors.push( | ||
new Error( | ||
`[webpack-extension-target] | ||
Path starts with "_" is preserved by the browser. | ||
The browser will refuse to load this extension. | ||
Please adjust your webpack configuration to remove that. | ||
File(s) starts with "_": | ||
` + with_.map((x) => ' ' + x).join('\n') | ||
) | ||
) | ||
} | ||
if (withTilde.length) { | ||
compilation.errors.push( | ||
new Error( | ||
`[webpack-extension-target] | ||
File includes "~" is not be able to loaded by Chrome due to a bug https://bugs.chromium.org/p/chromium/issues/detail?id=1108199. | ||
Please adjust your webpack configuration to remove that. | ||
If you're using splitChunks, please set config.optimization.splitChunks.automaticNameDelimiter to other char like "-". | ||
If you're using runtimeChunks, please set config.optimization.runtimeChunk.name to a function like | ||
entrypoint => \`runtime-\${entrypoint.name}\` | ||
File(s) includes "~": | ||
` + withTilde.map((x) => ' ' + x).join('\n') | ||
) | ||
) | ||
} | ||
}) | ||
} | ||
} |
122 changes: 122 additions & 0 deletions
122
...y/target-web-extension-plugin/rspack-target-webextension/RuntimeModules/AutoPublicPath.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import {type rspack} from '@rspack/core' | ||
import BrowserRuntime from '../BrowserRuntime' | ||
|
||
/** | ||
* @returns {import('webpack').RuntimeModule} | ||
*/ | ||
export function AutoPublicPathRuntimeModule( | ||
webpack: typeof rspack, | ||
acceptWeak: boolean | ||
) { | ||
const { | ||
RuntimeModule, | ||
RuntimeGlobals, | ||
Template, | ||
javascript: {JavascriptModulesPlugin} | ||
} = webpack | ||
|
||
class AutoPublicPathRuntimeModule extends RuntimeModule { | ||
constructor() { | ||
super('publicPath', RuntimeModule.STAGE_BASIC) | ||
} | ||
|
||
/** | ||
* @returns {string} runtime code | ||
*/ | ||
generate() { | ||
const {compilation} = this | ||
|
||
if (!compilation) | ||
return Template.asString( | ||
'/* [webpack-target-webextension] AutoPublicPathRuntimeModule skipped because no compilation is found. */' | ||
) | ||
const {scriptType, importMetaName} = compilation.outputOptions | ||
const chunkName = compilation.getPath( | ||
JavascriptModulesPlugin.getChunkFilenameTemplate( | ||
this.chunk, | ||
compilation.outputOptions | ||
), | ||
{ | ||
chunk: this.chunk, | ||
contentHashType: 'javascript' | ||
} | ||
) | ||
const outputPath = compilation.outputOptions.path | ||
if (!outputPath) | ||
return Template.asString( | ||
'/* [webpack-target-webextension] AutoPublicPathRuntimeModule skipped because no output path is found. */' | ||
) | ||
const undoPath = getUndoPath(chunkName, outputPath, false) | ||
|
||
return Template.asString([ | ||
...BrowserRuntime(acceptWeak), | ||
'var scriptUrl;', | ||
scriptType === 'module' | ||
? `if (typeof ${importMetaName}.url === "string") scriptUrl = ${importMetaName}.url` | ||
: Template.asString([ | ||
`if (${RuntimeGlobals.global}.importScripts) scriptUrl = ${RuntimeGlobals.global}.location + "";`, | ||
`var document = ${RuntimeGlobals.global}.document;`, | ||
'if (!scriptUrl && document && document.currentScript) {', | ||
Template.indent(`scriptUrl = document.currentScript.src`), | ||
'}' | ||
]), | ||
'// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration', | ||
'// or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.', | ||
'if (!scriptUrl) {', | ||
Template.indent([ | ||
'if (isChrome || isBrowser) scriptUrl = runtime.runtime.getURL("/");', | ||
'else throw new Error("Automatic publicPath is not supported in this browser");' | ||
]), | ||
'}', | ||
'scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/\\?.*$/, "").replace(/\\/[^\\/]+$/, "/");', | ||
!undoPath | ||
? `${RuntimeGlobals.publicPath} = scriptUrl;` | ||
: `${RuntimeGlobals.publicPath} = scriptUrl + ${JSON.stringify(undoPath)};` | ||
]) | ||
} | ||
} | ||
|
||
return new AutoPublicPathRuntimeModule() | ||
} | ||
|
||
/** | ||
* The following function (from webpack/lib/util/identifier) is not exported by Webpack 5 as a public API. | ||
* To not import anything from Webpack directly, this function is copied here. | ||
* | ||
* It follows the MIT license. | ||
* | ||
* @param {string} filename the filename which should be undone | ||
* @param {string} outputPath the output path that is restored (only relevant when filename contains "..") | ||
* @param {boolean} enforceRelative true returns ./ for empty paths | ||
* @returns {string} repeated ../ to leave the directory of the provided filename to be back on output dir | ||
*/ | ||
function getUndoPath( | ||
filename: string, | ||
outputPath: string, | ||
enforceRelative: boolean | ||
): string { | ||
let depth = -1 | ||
let append = '' | ||
outputPath = outputPath.replace(/[\\/]$/, '') | ||
for (const part of filename.split(/[/\\]+/)) { | ||
if (part === '..') { | ||
if (depth > -1) { | ||
depth-- | ||
} else { | ||
const i = outputPath.lastIndexOf('/') | ||
const j = outputPath.lastIndexOf('\\') | ||
const pos = i < 0 ? j : j < 0 ? i : Math.max(i, j) | ||
if (pos < 0) return outputPath + '/' | ||
append = outputPath.slice(pos + 1) + '/' + append | ||
outputPath = outputPath.slice(0, pos) | ||
} | ||
} else if (part !== '.') { | ||
depth++ | ||
} | ||
} | ||
return depth > 0 | ||
? `${'../'.repeat(depth)}${append}` | ||
: enforceRelative | ||
? `./${append}` | ||
: append | ||
} |
Oops, something went wrong.