用typescript开发一个vue的第三方插件(一)

基于微前端qiankun写一个vue专用的插件

本次项目的目的,是基于qiankun这个微前端框架写一个vue专用的插件来方便在vue的项目里使用。javascript

起初是想用js写的。但本着如今开发的是一个第三方类库,要考虑通用性。若是用js写,若是使用的项目时typescript的vue项目,就会遇到缺乏类型定义文件的问题。因而采用ts开发。这样申明文件的生成也变得容易一些。css

项目地址

https://github.com/Hades-li/qiankun-vuehtml

安装

yarn add qiankun-vue
npm install qiankun-vue --save

从vue-cli到手写webpack配置

vue-cli是vue官方的脚手架工具,傻瓜式构建项目,很是好用。但也许它更适合去开发一整套应用,而不是一个小小的第三方库。前端

目录结构

image.png

  • build: 存放webpack配置文件(后期手写)
  • dist:最终文件输出目录
  • example:用于展现用的列子。其实这个目录就是原来用vue-cli的生成的src目录,但因为咱们要开发一个第三方库,src目录是用来放库源代码的地方,因此就把目录给换了。
  • src:用于放源代码的地方。
  • types:typescript类型定义文件

用vue-cli官方构建方式(弃用)

vue-cli提供了一个官方的构建库方法vue

vue-cli-service build --target lib --name myLib [entry]

这样构建的结果就和官方展现的同样,会同时生成多个js的库文件。而实际在项目引用时,咱们只会用到一个。也就是说,咱们根本不但愿生成一堆咱们不想要的东西。java

修改vue.config.js构建(弃用)

放弃使用vue-cli官方自带的方法,剩下的方法就是改写vue.config.js配置来快速实现构建。如下是截取一些重要的代码段讲解node

if (isProd) {
      config.entry('index').clear().add('./src/index.ts')
      config.output
        .filename('index.js') // 输出文件名
        .libraryTarget('umd') // 打包类型
        .library('QiankunVue') // 全局变量名称
        ......
}

在生产环境下,入口文件为src路径下的入口文件。
输出文件设置成index.js,目标类型为umd格式,全局变量为QiankunVuejquery

if (!isProd) {
     config.entry('index').clear().add('./example/main.ts')
}

在开发环境下,目的不是把src的源代码打包输出,而是要运行example中的列子。因此就将入口文件设置成example下的main.ts文件。webpack

// 排除掉Vue
config.externals({
    vue: {
      root: 'Vue',
      commonjs: 'vue',
      commonjs2: 'vue',
      amd: 'vue'
    }
})

因为咱们开发的是vue的插件,因此须要将vue排除掉。git

此时,执行yarn run build后,dist目录中就会只生成一个index.js文件,固然,因为我并无屏蔽掉html模板插件,index.html仍是会被生成。

最终我放弃了以上两种vue-cli的构建方式,由于我发现,打包出来的index.js文件大小,高达120+KB。这个大小明显偏大。但我却找不到缘由。

webpack配置走起

此次改动,目的是作到彻底不依赖vue-cli,开发,生产环境彻底自定义。
image.png
build.js-采用函数式方式执行webpack打包。(暂时放弃)
如下是一些重点代码片断

webpack.config.base.js

基础配置,包含了生产和开发环境都须要配置

module.rules

// 预处理.ts文件
{
  test: /\.ts$/,
  use: [
    'babel-loader',
    {
      loader: 'ts-loader',
      options: {
        appendTsSuffixTo: [/\.vue$/],
        transpileOnly: true
      }
    }
  ],
  exclude: /node_modules/
},
// 预处理.vue文件
{
  test: /\.vue$/,
  loader: 'vue-loader'
},

这两段代码主要是预处理.ts文件和.vue文件。
babel-loader是可选的,做用是将es6代码转成es5用于兼容浏览器。现在的chrome,edge,firefox等现代浏览器,对于es6甚至es7支持已经很好了。不加babel-loader,打包文件尺寸能进一步缩小,运行效率还能更高一些,但本着也许还有人用ie的态度,顺手加一下吧。

// 预处理scss
    {
      test: /\.s[ac]ss$/,
      use: [
        env.NODE_ENV !== 'production' ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
        'css-loader',
        'sass-loader'
      ]
    },
    // 预处理css
    {
      test: /\.css$/,
      use: [
        env.NODE_ENV !== 'production' ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
        'css-loader'
      ]
    },

预处理scss,和css样式。其实在vue-cli构建出来的项目中,还能支持less,stylu另外两种预编译样式文件,但咱们如今是自定义webpack配置,本着,用到什么就配什么原则,只须要知足本身的需求便可。

// 预处理图片
{
  test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
  loader: 'url-loader',
  options: {
    limit: 4096,
    esModule: false, // 5.0版本以上要加
    fallback: {
      loader: 'file-loader',
      options: {
        name: 'img/[name].[hash:8].[ext]'
      }
    }
  }
}

预处理图片,这里要着重说一下这个配置项的坑。此配置用于解析图片/文件路径,以及对图片进行base64转换。起初,我是直接用vue-cli审查出了一个webpack配置文件,将这部分拷贝过来,和以上代码相比,仅仅是没有esModule: false这个参数。运行的结果是,全部图片不展现,查看图片路径是这样<img src=[object module]>。因而加上esModule:false就ok了。

以上缘由是因为vue-loader在对.vue文件解析时,

<img src="../image.png">

标签会被编译成

createElement('img', {
  attrs: {
    src: require('../image.png') // commonJS语法导入函数 如今这是一个模块的请求了
  }
})

require是commonJS规范的导入函数。而url-loader默认是识别es6的导入语法,即import。因而,最终转换出的代码就没法正常显示图片。esModule: false就是启用commonJS引入方式。

那为何vue-cli构建出来的项目则不须要配置这个参数?
由于vue-cli所采用的url-loader版本号还停留在2.x.x。而最新版本已经到了4.x.x。旧版本url-loader是没有这个限制机制。新版url-loader是鼓励采用es6的标准规范来引入文件。commonJs是nodejs的规范。

plugins

plugins: [
      new VueLoaderPlugin(), // 配合vue-loader
      new ForkTsCheckerWebpackPlugin(), // 将ts-loader类型检查跑在一个独立线程加速编译
      new webpack.DefinePlugin({
        'process.env': {
          NODE_ENV: '"' + env.NODE_ENV + '"',
          BASE_URL: '"/"'
        }
      }), // 给浏览器代码中添加全局变量
      new FriendlyErrorsPlugin(), // 友好的错误提示
      new MiniCssExtractPlugin(), // css提取
      new CaseSensitivePathsPlugin() // 严格路径大小写
    ]

以上这些插件基本都是看着vue-cli审查加入的。有的能够不加,但加了,对于开发都是有极大帮助的。

webpack.config.prd.js

生产环境下的配置。

module.exports = merge(baseConf({ NODE_ENV: env }), {
  mode: env,
  entry: './src/index.ts',
  output: {
    path: resolve('dist'),
    filename: 'index.js',
    publicPath: '/',
    chunkFilename: 'js/[name].[contenthash:8].js',
    libraryTarget: 'umd',
    library: 'QiankunVue'
  },
  externals: {
    vue: {
      root: 'Vue',
      commonjs: 'vue',
      commonjs2: 'vue',
      amd: 'vue'
    }
  },
  plugins: [
    // 清理dist文件夹
    new CleanWebpackPlugin()
  ]
})

生产环境配置较为简单,首先经过webpack-merge这个插件将webpack.config.base.js的配置合并过来,添加entry,和output的配置项,而且用cleanwebpackPlugin插件清理一下dist目录,就能够一键打包了。

webpack.config.dev.js

开发环境下的配置。

entry: './example/main.ts',
  output: {
    path: resolve('dist'),
    filename: 'app.js',
    publicPath: '/'
  },
  devServer: {
    contentBase: resolve('dist'),
    port: 8080,
    hot: true,
    progress: true, // 0-100%的进度提示
    quiet: true // 去掉一堆告警信息
  },
  devtool: 'eval-source-map',

开发环境,一样合并webpack.config.base.js,改写一下entry指向example/main.ts。
配置一下开发服务器devServer,切记,别忘了安装webpack-dev-server这个包。

plugins

plugins: [
    new webpack.HotModuleReplacementPlugin(), // 支持热模块替换
    new HtmlWebpackPlugin({
      title: 'qiankun',
      template: resolve('public/index.html'),
      favicon: resolve('public/favicon.ico')
    }), // 配置index.html模板
    new CopyWebpackPlugin([
      {
        from: resolve('public'),
        to: resolve('dist'),
        toType: 'dir',
        ignore: ['index.html']
      }
    ]) // 拷贝插件,用于拷贝一些不参与打包的静态资源至dist目录
  ]

这些都是开发模式下必要的插件

以上的这些webpack配置,比起vue-cli提供的要少不少,好比咱们甚至都没有针对.js文件编译loader配置项,由于咱们的项目中纯玩ts。实际用vue-cli构建的ts项目是能够进行js,ts混合开发的。但纯手写webpack的目的,就是只配本身须要的。

本期重点介绍webpack配置,这也是此项目里难点之一,耗时费力。

遗留问题1-没法自动生成*.d.ts文件

开发一个标准的js库,无论你是用js开发仍是ts,现在,都是要给本身的库写ts的类型申明文件。像相似lodash,jquery这些著名的库,早期都是没有申明文件。致使若是用于ts项目开发,类型检测机制就没法进行(早期没有typescript)。但如今,这些库都已经将类型申明补充进来了。
类型申明能够手写,但太费事。最好固然是自动生成。
按照官方说法,在tsconfig.js中配置了declaration: true的属性时,就能够自动生成。但实际是没有任何反应。固然,你若是直接用tsc --declaration命令行编译,则能够生成,目前这是个遗留问题。若是有大神知道解决方法忘指教。

相关文章
相关标签/搜索