chrome插件教程终-使用vue来开发chrome插件

滞波柯里化
• 阅读 6966

前面,我们已经了解了各页面之间的通信. 权限等一系列的操作. 已经掌握了基本可以开发chrome扩展的技能. 剩下的就是需要用到某些api查看一下api文档就能开发了.对于当代前端来说. 我们仅仅用原生的js和html来写我们的代码. 这样就提现不出我们的价值了. 这里我们就根据之前的一些代码. 来处理我们的现代框架vue来开发我们的插件.

技术栈

这里我们所需要使用的技术栈如下

scss
webpack5
vue3
vue-router4
vant3

这里我们使用的都是最新的版本. 就是要shuai

规划打包后的目录结构

首先,我们得想明白我们需要打包成什么样的目录. 我们根据之前的示例. 我们定义出如下的目录

background.js
manifest.json
assets
content.js
option.html
option.js
popup.html
popup.js
devtool.html
devtool.js
devtool/panel.html
devtool/panel.js
devtool/sidebar.html
devtool/sidebar.js

这是我们打包过后最终的目录. 有了一个大概的结构过后,我们就开始来处理我们的代码.

初始目录

我们规划好目录过后, 就需要处理我们的项目路径了. 随意建立一个目录. 然后使用如下命令.

npm init

如果没有node. 请自行安装. 我这里就不做过多的描述了. 执行过后. 就有一堆输入协议之类的东西. 所有操作完毕过后. 就有了我们的package.json. 最主要的就是初始化这个json文件. 我们好方便自己操作. 首先安装我们的基础. webpack.

npm install webpack --save-dev

完成过后. 我们就需要对我们的项目目录得有所规划了.

规划项目目录

首先. 我们的最外层肯定都的是一些全局型的配置和目录. 例如babel.config.js. 例如package.json这些都在最外层. 我们的核心代码都放到src目录下面. 有一个专门用于打包文件代码存储的地方. 我们chrome的入口肯定也得有一个. 放到src下面. 有了以上基本的东西. 就能得到我们大概的文件目录是什么样子的了. 我的文件目录如下.

build // webpack打包配置
dist // 打包过后的文件
node_modules // npm模块
public // 基础文件
src // 核心代码
src/entry // chrome所有页面的入口
src/routers // 我们自己定义的路由.
src/views // chrome的所有页面
src/content.js // content script 入口
src/background.js // service worker入口
utils // 工具类

webpack

首先我们的定义我们webpack打包时的入口. 由于我们有开发时候的环境和生成环境. 我们我们build文件夹下面就有以下3个文件

base.js // 基础配置文件等
dev.js // 开发模式下的配置
prod.js // 生产环境时的配置.

首先. 我们要定义我们的base.js. 这里面有几个点. 首先. 我们的入口文件是有多个. 及src/entry下面则有多个. 所以我们得定义我们的入口.如下

/* global module, require, __dirname */
const path = require('path')
const utils = require('../utils/util')
// 导出默认的配置信息. 其他的先不管.
module.exports = {
  entry: utils.findEntry(),
  output: {
    path: path.resolve(path.dirname(__dirname), 'dist'),
    filename: '[name].js',
    publicPath: './',
    clean: true,
    libraryTarget: 'umd'
  }
}

// utils/util
/*global require, __dirname, module */
const path = require('path')
const fs = require('fs')

function findEntry() {
  const entry_path = path.dirname(__dirname) + '/src/entry'
  const modules = []
  const entries = {}
  const dirs = fs.readdirSync(entry_path)

  dirs.forEach(function(item) {
    const full_path = path.join(entry_path, item)
    const stat = fs.statSync(full_path)

    if (stat.isDirectory()) {
      modules.push(full_path)
    }
  })

  // 获取所有的文件夹下面所有的入口文件.
  if (modules.length <= 0) {
    return {}
  }

  modules.map(function(item) {
    const entry = fs.statSync(item + '/main.js')
    if (!entry.isFile()) {
      return
    }

    const info = path.parse(item + '/main.js')

    const dirname = info.dir.split('/').pop()

    entries[dirname] = item + '/main.js'
  })
  
  

  return entries
}

module.exports = {
  findEntry: findEntry,
}

定义完过后. 我们的enrty入口大概就是这样的样子.

{
   "popup": "src/entry/popup/main.js", // 绝对路径
}

有了这个过后. 就相当于我们有了一个main.js的入口. 同时. 我们需要增加webpack的输出的配置. 此外. 我们还要告知webpack能处理哪些文件. 在base.js里面加入下面的代码.

  resolve: {
    // 可以处理js,json,vue文件
    extensions: [
      '.js', '.vue', '.json'
    ]
  },

另外. 我们还得对一些文件按照怎样的格式进行处理. 比方说.vue

引入vue

执行一下命令

npm install vue@next vue-router@next --save
npm install vue-loader@next

在build/base.js告知怎么处理vue文件.

const { VueLoaderPlugin } = require('vue-loader')

// 导出时增加如下配置
module: {
    rules: [
      // 指定vue文件使用vue-loader来进行解析.
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
},
plugins: [
  new VueLoaderPlugin()
]

这样就能处理我们的vue.

引入babel

为撒我们需要babel. 是因为各个浏览器对js的兼容性都不是特别好. 所以我们需要对兼容性进行处理. 先执行命令

npm install @babel/preset-env babel-loader babel-plugin-import core-js -D

然后在build/base.js中module.rules里面增加如下配置

{
        test: /\.js$/,
        loader: 'babel-loader'
}

启用对babel的加载.

引入css/scss

在css中. 可以使用style-loader来直接打包css. 但是我们要使用scss. 所以. 我们就需要引入sass-loader. 同时还要处理兼容postcss. 所以. 执行如下命令

npm install -D css-loader node-sass sass-loader postcss-loader 

然后同babel一样. 在build/base.js中的rules增加如下配置

{
        test: /\.(css|scss|sass)$/,
        use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
}

这样就增加了对css的处理.

引入vant

直接使用

npm i vant@3

然后由于我们使用了babel. 直接使用按需加载方式. 在跟路径创建babel.config.js. 加入如下代码.

// babel.config.js
/* global module */
const presets = []
const plugins = [
  [
    'import',
    {
      'libraryName': 'vant',
      'libraryDirectory': 'es',
      'style': true
    }
  ]
]

module.exports = {
  plugins,
  presets
}

即可完成引用.

处理特殊入口

前面我们使用了utils.findEntry函数来处理入口. 但是对于我们特殊的. 比如sidebar和panel. 这种用于控制台的入口. 需要在devtools.js里面进行创建. 所以. 我们就将devtools.js给单独打包.跟content, 所以修改我们的findEntry函数.如下

function findEntry() {
  const app_path = path.dirname(__dirname)
  const entry_path = path.dirname(__dirname) + '/src/entry'
  const modules = []
  const entries = {}
  const dirs = fs.readdirSync(entry_path)

  dirs.forEach(function(item) {
    const full_path = path.join(entry_path, item)
    const stat = fs.statSync(full_path)

    if (stat.isDirectory()) {
      modules.push(full_path)
    }
  })

  // 获取所有的文件夹下面所有的入口文件.
  if (modules.length <= 0) {
    return {}
  }

  modules.map(function(item) {
    const entry = fs.statSync(item + '/main.js')
    if (!entry.isFile()) {
      return
    }

    const info = path.parse(item + '/main.js')

    let dirname = info.dir.split('/').pop()

    if (['panel', 'sidebar'].indexOf(dirname) > -1 ) {
      dirname = 'devtools/' + dirname
    }

    entries[dirname] = item + '/main.js'
  })

  if (fs.statSync(app_path + '/src/content.js').isFile()) {
    entries['content'] = app_path + '/src/content.js'
  }

  if (fs.statSync(app_path + '/src/background.js').isFile()) {
    entries['background'] = app_path + '/src/background.js'
  }

  if (fs.statSync(app_path + '/src/devtools.js').isFile()) {
    entries['devtools'] = app_path + '/src/devtools.js'
  }

  return entries
}

同时. 处理了content. background.devtools.这几个还得处理特殊的panel和sidebar. 就直接放到entries里面就可以. 但是仅仅这样是不行的. 我们还需要将html代码给打包处理.这我们使用了html-webpack-plugin. 直接安装

npm i html-webpack-plugin -D

同时在build/base.js的module.exports对象的plugins里面加入如下代码.

// 
[
     new VueLoaderPlugin(),
].concat(utils.genHtmlPlugins())
// 在utils.genHtmlPlugins
// 这里要做一些特殊处理才能进行打包. 对对应的sidebar, panel来进行打包. 主要是要注入代码才可以.
function genHtmlPlugins() {
  const entires = findEntry()
  const plugins = []
  const template = path.dirname(__dirname) + '/public/extension.html'
  const modules = Object.keys(entires)

  // 这里有问题. 需要重新改动一下就可以了.
  for (var index in modules) {
    const module_name = modules[index]
    const name = module_name.split('/').pop()

    if (['content', 'background'].indexOf(module_name) > -1) {
      continue
    }

    // 打包对应的模块到指定的目录.其余就不打包了.
    const filename = module_name + '.html'

    plugins.push(new HtmlWebpackPlugin({
      // publicPath: './devtools',
      title: name,
      template: template,
      name: name,
      filename: filename,
      chunks: [module_name],
      inject: 'body',
      minify: {
        removeComments: true // 自动删除注释
      }
    }))
  }

  return plugins
}

这样我们就能打包好对应的代码了.

复制公共文件

我们打包好基本代码过后, 还需要将public里面的公共文件进行复制. 直接使用copy-webpack-plugin. 先执行命令

npm i copy-webpack-plugin -D

按照如下方式使用

plugins: [
    // .vue文件打包
    new VueLoaderPlugin(),
    // 直接将原始文件复制过去.
    new CopyWebpackPlugin({
      patterns: [
        // 将src/manifest.json直接复制过去.
        { from: path.resolve(path.dirname(__dirname), 'src/manifest.json'), to: 'manifest.json' },
        { from: path.dirname(__dirname) + '/public', filter: async(file_path) => {
          const app_path = path.dirname(__dirname)

          if (file_path.indexOf('extension.html') > 0) {
            return false
          }

          if (file_path.indexOf('devtools.html') > 0) {
            // 如果不存在src/entry/panel和src/entry/sidebar. 就不复制了.
            if (!fs.statSync(app_path + '/src/entry/panel').isDirectory() && !fs.statSync(app_path + '/src/entry/sidebar').isDirectory()) {
              return false
            }
          }

          return true
        } }
      ]
    })
  ].concat(utils.genHtmlPlugins())

这样. 打包就好了.

额外

不过有一些不是特别完美. 打包后会生成.LISENCE.txt这种文件. 直接使用terser-webpack-plugin插件. 先安装

npm i terser-webpack-plugin -D

然后按照如下使用

const TerserPlugin = require('terser-webpack-plugin')

// 在module.exports对象中增加
optimization: {
    minimizer: [new TerserPlugin({
      extractComments: false
    })]
 }

另外. 打包好的东西如果直接将dist文件夹当成扩展来处理的话.会直接报错. 这是因为webpack如果是dev开发模式. 会以eval这种形式来加载js. 在chrome里面是不允许的. 所以我们需要做特殊处理. 这里是我的build/dev.js和build/prod.js

// dev.js
const { merge } = require('webpack-merge')
var base_config = require('./base')

// 重新处理一下.
module.exports = merge(base_config, {
  mode: 'production',
  // 指定入口.
  watch: true
})

// prod.js
const { merge } = require('webpack-merge')
var base_config = require('./base')

// 重新处理一下.
module.exports = merge(base_config, {
  mode: 'production'
})

这里都使用production来进行打包. 这样就配置好了. 不过基础的入口代码,eslint等没提供. 这些的话. 请查看源代码.

最后

基础的也就完了. 对于大家来应该已经入门. 剩下的就是靠文档来处理了. 例如不知道api的. 就需要去查找一下chrome的api.

// 源代码
https://github.com/AdolphGithub/crx-template
// api文档
https://developer.chrome.com/docs/extensions/reference/

如果大家觉得chrome的开发模板在使用中. 有问题. 请前往github创建issue. 我会定期查看代码.

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
6个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这