tips: 本框架目前支持options和popup页面打包,background.js、content.js和devtool打包目前暂未适配。
背景
在开发完baymax插件之后,把常用的二维码、网址链接也加到这个插件里了,以后还肯能会添加更多功能,就让这个插件变得臃肿。便想着代码重构,把它做成可配置的,可以通过options页面配置想要的模块在popup中展示。既然要重构,便采用了比较新的webpack5、vue3、ts技术栈。
chrome插件开发和普通web开发的差异
差异有以下几点:
- chrome插件开发环境是多页面的,需要独立配置options、popup等页面
const chromeName = ['popup', 'options']
let entryObj = {}
chromeName.forEach(name => {
entryObj[name] = path.resolve(__dirname, `src/${name}/main.ts`)
})
const htmlWebpackPluginList = chromeName.map(name => {
return new HtmlWebpackPlugin({
template: 'index.html',
filename: `${name}.html`,
chunks: [name]
})
})
const config ={
entry: entryObj,
plugins: [
...htmlWebpackPluginList,
]
}
- 另外manifest.json、静态资源也需要复制到最终输出的目录中
const fileCopyList = [
{
from: path.resolve('src/manifest.json'),
to: `${path.resolve('dist')}/manifest.json`
},
{
from: path.resolve('src/assets/logo.png'),
to: `${path.resolve('dist')}/img/logo.png`
},
{
from: path.resolve('src/background/background.js'),
to: `${path.resolve('dist')}/js/background.js`
},
{
from: path.resolve('src/content/content.js'),
to: `${path.resolve('dist')}/js/content.js`
},
]
{
new CopyWebpackPlugin({patterns: fileCopyList}),
}
- 热更新的差异,普通web开发输出的资源是在内存中,chrome插件开发则要输出到磁盘中,另外需要设置disableHostCheck: true,跳过host检查,open: false,避免自动打开网页。
devServer: {
contentBase: false,
// publicPath: './dist',
hot: true,
port: 8080,
open: false,
lazy: false,
// hotOnly: true,
disableHostCheck: true,
compress: true,
overlay: true,
writeToDisk: true,
},
完整的webpack配置如下:
const path = require('path');
const { HotModuleReplacementPlugin, IgnorePlugin } = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin')
const chromeName = ['popup', 'options']
const fileCopyList = [
{
from: path.resolve('src/manifest.json'),
to: `${path.resolve('dist')}/manifest.json`
},
{
from: path.resolve('src/assets/logo.png'),
to: `${path.resolve('dist')}/img/logo.png`
},
{
from: path.resolve('src/background/background.js'),
to: `${path.resolve('dist')}/js/background.js`
},
{
from: path.resolve('src/content/content.js'),
to: `${path.resolve('dist')}/js/content.js`
},
]
let entryObj = {}
chromeName.forEach(name => {
entryObj[name] = path.resolve(__dirname, `src/${name}/main.ts`)
})
const htmlWebpackPluginList = chromeName.map(name => {
return new HtmlWebpackPlugin({
template: 'index.html',
filename: `${name}.html`,
chunks: [name]
})
})
const config = {
mode: 'development',
entry: entryObj,
output: {
filename: 'js/[name].js',
path: path.resolve(__dirname, 'dist')
},
devServer: {
contentBase: false,
// publicPath: './dist',
hot: true,
port: 8080,
open: false,
lazy: false,
// hotOnly: true,
disableHostCheck: true,
compress: true,
overlay: true,
writeToDisk: true,
},
watchOptions: {
ignored: /node_modules/
},
plugins: [
new CleanWebpackPlugin(),
...htmlWebpackPluginList,
new IgnorePlugin(/^\.\/locale$/, /moment$/),
new HotModuleReplacementPlugin(),
new VueLoaderPlugin(),
new ForkTsCheckerWebpackPlugin({ // ? fork一个进程进行检查,并设置async为false,将错误信息反馈给webpack
async: false,
}),
new CopyWebpackPlugin({patterns: fileCopyList}),
],
module: {
rules: [
// babel使用runtime,避免将不需要的代码注入
{
test: /\.js$/,
exclude: /node_modules/,
use: [{
loader: 'babel-loader',
options: {
// cacheDirectory: true,
presets: ['@babel/preset-env'],
plugins: [
'@babel/plugin-transform-runtime',
['import', {
"libraryName": "antd",
"style": true, // or 'css'
}, 'antd']
]
}
}],
},
{
test: /\.ts$/,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader',
options: {
// 指定特定的ts编译配置,为了区分脚本的ts配置
configFile: path.resolve(__dirname, './tsconfig.json'),
appendTsSuffixTo: [/\.vue$/],
transpileOnly: true,
}
}
]
},
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader',
{
loader: 'less-loader',
options: {
lessOptions: {
javascriptEnabled: true,
}
}
}],
}
]
},
resolve: {
extensions: ['.js','.vue', '.json', '.ts']
},
};
module.exports = (env) => {
console.log(`当前执行${env.mode}模式`);
return config;
}
package.json配置:
{
"name": "baymax-vue3",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server --config webpack.dev.config.js --env.mode development --watch --profile",
"prod": "webpack --config webpack.prod.config.js --env.mode product"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.14.6",
"@babel/plugin-transform-runtime": "^7.14.5",
"@babel/preset-env": "^7.14.7",
"@vue/compiler-sfc": "^3.1.4",
"babel-loader": "^8.2.2",
"babel-plugin-import": "^1.13.3",
"clean-webpack-plugin": "^4.0.0-alpha.0",
"copy-webpack-plugin": "^9.0.1",
"css-loader": "^6.0.0",
"fork-ts-checker-webpack-plugin": "^6.2.12",
"html-webpack-plugin": "^5.3.2",
"less": "^4.1.1",
"less-loader": "^10.0.1",
"style-loader": "^3.1.0",
"ts-loader": "^9.2.3",
"typescript": "^4.3.5",
"vue-loader": "^16.3.0",
"webpack": "^5.44.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.2"
},
"dependencies": {
"@babel/runtime": "^7.14.6",
"ant-design-vue": "^2.2.2",
"vue": "^3.1.4",
"vuex": "^4.0.2"
}
}
tsconfig.json配置
{
"compilerOptions": {
// "esModuleInterop": true,
"incremental": true, // 增量编译
"allowJs": true,
"target": "esnext",
"module": "esnext",
// 这样就可以对 `this` 上的数据属性进行更严格的推断`
"strict": true,
"moduleResolution": "node",
"resolveJsonModule": true,
},
"exclude": [
"node_modules"
]
}
目录结构:
image.png