是這樣的,最近想要開(kāi)發(fā)一個(gè)瀏覽器拓展的應(yīng)用,剛開(kāi)始就給我惡心到了,每天都用著第三方模塊和 import export 切分文件的我面對(duì)一個(gè)個(gè)獨(dú)立的js竟無(wú)從下手,那么就用webpack來(lái)構(gòu)建一個(gè)模塊化的 extention 項(xiàng)目吧~
項(xiàng)目地址:https://gitee.com/boboanzuiniubi/ext-xhr-proxy
這個(gè)是個(gè)xhr劫持的拓展工具,我會(huì)在做完功能之后,把項(xiàng)目模板拆出來(lái)~
先分析一下要做什么吧
manifest.json:
{
"manifest_version": 2,
"name": "zcr",
"description": "ceshi",
"version": "1.0",
"content_scripts": [{ "matches": ["<all_urls>"], "css": [], "js": ["./content_scripts/inject_xhr.js"] }]
}
manifest.json 是位于項(xiàng)目根目錄下的拓展應(yīng)用的清單文件,這里面是拓展應(yīng)用的描述,和 content_scripts/page/icon 等等資源的地址。大多屬性是靜態(tài)的,資源地址 是整個(gè)構(gòu)建流程需要處理的,要做的是將構(gòu)建后的資源目錄,寫(xiě)入 manifest.json ,亦或者是按照 manifest 上寫(xiě)的路徑去構(gòu)建資源。
load scripts:
拓展中的 content_script 和 inject_script 都是需要打包起來(lái)的 js 文件,各種單頁(yè)面 page 也需要打包一個(gè)入口文件,那我們就需要寫(xiě)一段 js 去按路徑讀取這些 js 入口文件,并且要將output的文件地址寫(xiě)入 manifest
load pages:
拓展(extention)中是有一些頁(yè)面的,比如 popup page / background page / devtools page 都是一個(gè)html,我們可以用 html-webpack-plugin 和三大框架構(gòu)建一個(gè)單頁(yè)面應(yīng)用來(lái)快速開(kāi)發(fā)
目錄結(jié)構(gòu)與構(gòu)建流程

細(xì)化
eslint
(todo 但又不完全todo) 在糾結(jié)要不要引,因?yàn)轫?xiàng)目不是很大,不是很關(guān)鍵
friendly-errors-webpack-plugin
friendly-errors-webpack-plugin用來(lái)輸出webpackl的報(bào)錯(cuò)真是簡(jiǎn)單又好用,省得你去研究怎么輸出異常。
plugin
我這里用的 plugin 是為了在每個(gè)模塊構(gòu)建完成時(shí),記錄一下需要寫(xiě)入 manifest 的 output 地址的
找一個(gè)合適的鉤子獲取 output 的地址

比如傳入一個(gè)記事本對(duì)象,并在構(gòu)建結(jié)束后重寫(xiě)manifest.json
const manifest_content_scripts = {
matches: ['<all_urls>'],
css: [],
js: []
}
config.plugins.push(new ContentScriptPlugin(manifest_content_scripts))
webpack(config, (err, stats) => {
if (err || stats.hasErrors()) {
} else {
// 重寫(xiě)manifest
fs.writeFileSync(path.resolve(__dirname, '../output/manifest.json'), JSON.stringify({
...manifest,
web_accessible_resources: mainfest_web_accessible_resources,
content_scripts: [manifest_content_scripts]
}))
}
});
...
// 這個(gè)plugin在構(gòu)建模塊時(shí),記錄一條需要注入的content_script
const isContentJs = (name) => /content_scripts\/.+\.js$/.test(name)
const isContentCss = (name) => /content_scripts\/.+\.css$/.test(name)
module.exports = class ContentScriptPlugin {
constructor(manifest_content_script) {
this.manifest_content_script = manifest_content_script
}
apply(compiler) {
compiler.hooks.assetEmitted.tap(
'ContentScriptPlugin',
(file, { content, source, outputPath, compilation, targetPath }) => {
if (isContentJs(file)) {
this.manifest_content_script.js.push(file)
} else if (isContentCss(file)) {
this.manifest_content_script.css.push(file)
}
}
)
}
}
react
用 react 加其周邊的組件庫(kù) 可以很快速的開(kāi)發(fā) html 頁(yè)面,需要babel-loader和html-webpack-plugin去執(zhí)行jsx語(yǔ)法轉(zhuǎn)換和創(chuàng)建頁(yè)面。
配置 babel
// babel.config.json
{
"presets": [
"@babel/env",
"@babel/preset-react"
]
}
添加 rules
// rules 只處理頁(yè)面部分的js就可以了,可以加載ext的瀏覽器并沒(méi)有兼容性問(wèn)題
rules: [{
test: /\.jsx?$/,
loader: "babel-loader",
include: [src('./popup')]
},
...
]
打包入口
entry: {
...content_scripts,
...inject_scripts,
background: src('./background.js'),
popup: src('./popup/index.js')
},
配置 html plugin
plugins: [
new HtmlWebpackPlugin({
filename: 'popup.html',
template: src('./popup/index.html'),
chunks: ['popup']
}),
...
],
shelljs
用 node 的 file system 處理文件會(huì)有各個(gè)node版本fs api的兼容性問(wèn)題,太煩了,用shelljs兼容性問(wèn)題會(huì)少點(diǎn)
file-loader
拓展應(yīng)用中的資源文件會(huì)有 icon img 這種 圖片資源,統(tǒng)一就用file-loader 去放到一個(gè)img目錄下好了,在manifest中就按照命名引用img目錄下的資源
rules: [
...
,{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'file-loader',
options: {
name: 'img/[name].[ext]'
}
}
]
}]