是这样的,最近想要开发一个浏览器拓展的应用,刚开始就给我恶心到了,每天都用着第三方模块和 import export 切分文件的我面对一个个独立的js竟无从下手,那么就用webpack来构建一个模块化的 extention 项目吧~
项目地址:https://gitee.com/boboanzuiniubi/ext-xhr-proxy
这个是个xhr劫持的拓展工具,我会在做完功能之后,把项目模板拆出来~
先分析一下要做什么吧
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 是位于项目根目录下的拓展应用的清单文件,这里面是拓展应用的描述,和 content_scripts/page/icon 等等资源的地址。大多属性是静态的,资源地址 是整个构建流程需要处理的,要做的是将构建后的资源目录,写入 manifest.json ,亦或者是按照 manifest 上写的路径去构建资源。
load scripts:
拓展中的 content_script
和 inject_script
都是需要打包起来的 js 文件,各种单页面 page 也需要打包一个入口文件,那我们就需要写一段 js 去按路径读取这些 js 入口文件,并且要将output的文件地址写入 manifest
load pages:
拓展(extention)中是有一些页面的,比如 popup page / background page / devtools page 都是一个html,我们可以用 html-webpack-plugin
和三大框架构建一个单页面应用来快速开发
目录结构与构建流程
细化
eslint
(todo 但又不完全todo) 在纠结要不要引,因为项目不是很大,不是很关键
friendly-errors-webpack-plugin
friendly-errors-webpack-plugin
用来输出webpackl的报错真是简单又好用,省得你去研究怎么输出异常。
plugin
我这里用的 plugin 是为了在每个模块构建完成时,记录一下需要写入 manifest 的 output 地址的
找一个合适的钩子获取 output 的地址
比如传入一个记事本对象,并在构建结束后重写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 {
// 重写manifest
fs.writeFileSync(path.resolve(__dirname, '../output/manifest.json'), JSON.stringify({
...manifest,
web_accessible_resources: mainfest_web_accessible_resources,
content_scripts: [manifest_content_scripts]
}))
}
});
...
// 这个plugin在构建模块时,记录一条需要注入的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 加其周边的组件库 可以很快速的开发 html 页面,需要babel-loader
和html-webpack-plugin
去执行jsx语法转换和创建页面。
配置 babel
// babel.config.json
{
"presets": [
"@babel/env",
"@babel/preset-react"
]
}
添加 rules
// rules 只处理页面部分的js就可以了,可以加载ext的浏览器并没有兼容性问题
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 处理文件会有各个node版本fs api的兼容性问题,太烦了,用shelljs
兼容性问题会少点
file-loader
拓展应用中的资源文件会有 icon img 这种 图片资源,统一就用file-loader
去放到一个img目录下好了,在manifest中就按照命名引用img目录下的资源
rules: [
...
,{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'file-loader',
options: {
name: 'img/[name].[ext]'
}
}
]
}]