记录封装npm业务组件遇到的坑

背景

基于vue2已经开发了一个选择器web app,是单页应用。后续需求是要把这个spa变成一个npm组件,因为要考虑其他项目的使用。以下是一些改造的心得以及遇到的坑以及解决方案。

改造前的技术栈

vue2.7.x + @vue/cli-service + vue-i18n + vue-router + vuex + elementUI2.x + axios

除此之外,还安装了一些公司内部开发的包,影响的范围是在打包的时候会对文件做一些操作

评估与改造

平时如果只是封装一个sdk,就只是一些js代码,最多也就把必要的第三方npm包也打包进去,npm包体积不会太大。

但如果是业务组件,要考虑包的大小,考虑到宿主项目的UI框架不兼容,所以也要把UI框架也要打进去,至于其他第三方包,能省则省,该externals掉就externals掉。

盘点了下,以下是可以通过改代码,把以下第三方包直接省掉,以达到减少包大小的效果:

vue-router,我们可以用component来替换不同的组件,这样就可以平替掉vue-router

vuex,可以在根组件使用provide,为了在子组件、孙子组件inject后使用保留响应式,需要provide时,直接传this。如果有看过ElementUI2.x源码的同学肯定不陌生,这样就可以平替掉vuex

js 复制代码
// 父组件
data() {
    return {
        a: 1,
        b: 2
    }
},
provide() {
    return {
        docSelector: this
    }
},
js 复制代码
// 子组件
inject: ['docSelector'],
methods: {
    test() {
        console.log(this.docSelector.a)
        this.docSelector.b = 3
    }
}

vue-i18n,自己实现一个class,一个属性是用记录当前使用的多语言,一个方法是根据keyword读取多语言文件的字段返回值。以及需要写一个方法类似$t作为mixins导入到每个SFC,这样子就可以平替掉vue-i18n

遇到的问题

npm包使用的vue版本跟宿主项目的vue版本不一致导致的坑

这里直接先说结论:npm包需要externals的包,版本要尽可能的低,以兼容宿主项目的包。几个例子,npm项目vue@2.7.x,而宿主项目的vue2.6.x,这时候,宿主项目打包出来的产物,会有2份vue代码。因为版本不兼容。

经过评估,因为我们的npm项目依赖了2.6.x的Observer属性,所以npm项目需要从vue@2.7.x降级到vue@2.6.0,并且配置package.jsonpeerDependencies,这个字段表示某个包期望宿主项目中已经存在的依赖,而不是由包本身直接安装。而且npm包打包的时候也要配置externals掉。

json 复制代码
{
  "peerDependencies": {
    "vue": ">2.6.0"
  }
}

以这个配置为例:

  1. 如果宿主项目的vue版本>2.6.0,打包之后,就只会有一份vue代码;
  2. 如果宿主项目的vue版本<2.6.0,打包之后,就会有两两份vue代码;
  3. 如果使用npm link本地调试,当且仅当npm包的vue版本 === 宿主项目的vue版本,才会打包一份vue代码,否则会有两份vue代码。举例子说,npm项目vue@2.6.14以及宿主项目vue@2.6.14,打包只会有一份vue代码;npm项目vue@2.6.11,宿主项目vue@2.6.14,打包就会有两份vue代码。

为什么要考虑这个问题?因为遇到了以下的问题,也经历了好几天的debug,通过debugger仔细观察才知道有两份vue,才去思考为什么。

问题表现:

  1. 在项目内写的测试demo,可以正常展示el-tooltipel-loadingel-messages组件,但是在宿主项目项目npm link来加载本地包时,其他UI组件都可以正常展示,但是这几个组件不能正常显示。
  2. 发布正式npm包到私有npm仓库,宿主项目有些安装之后是正常显示,有些是不能正常显示对应的组件

看了el-tooltipel-loadingel-messages还有类似的源码,发现了会在SFC引入import Vue from 'vue',去做一些实现,譬如el-tooltip就是通过这个方法去做一些trick影响子组件的渲染顺序。所以这里的vue就会以npm项目的vue版本为准,所以如果版本对不上,就会出现两份。

样式隔离

  1. 如果UI库自带修改UI库css名前缀,就直接改,不行的话可以写插件,用ast改。见这里:github.com/JimFirst/ch...
  2. 如果有全局reset样式,也要手动改css名前缀
  3. vue sfc记得加上scope -><script scoped>

知识碎片补充

  1. 项目是用公司内部的cli脚手架搭建的,它实现了一键安装vue-router vuex vue-i18n,但是是使用了require来导入几个文件实现的。我发现哪怕我没有引用这三个包,但是最终打包还是会打进去,没有tree-shake掉,所以我自己改掉了用require引用的方法,才可以把这些包去掉。
  2. 之前都是直接配置webpack配置,对@vue/service配置不是特别熟悉,所以遇到问题都是直接看源码+debugger,基本都可以解决问题。记录一些代码片段:
json 复制代码
// package.json
"build:prod": "cross-env NODE_ENV=prod vue-cli-service build --mode production --report --skip-plugins xxxx,yyyy --target lib --name zzzzz --dest libdist libsrc/index.js",
  • 上文提到项目依赖了内部的一些npm包,而且会影响打包产物。如果需要跳过这些插件,我发现vue-cli-service官方的文档也没有介绍--skip-plugins这个字段是可以跳过某些插件的,我也是看源码才知道
js 复制代码
const env = process.env.NODE_ENV;
const path = require("path");
module.exports = {
  configureWebpack: (config) => {
    // 项目入口配置
    config.entry = {
      app: path.resolve(__dirname, "./main.js"),
    };
    config.mode = "production";
  },
  chainWebpack: (config) => {
    // 删除名为 svgs 的规则
    config.module.rules.delete("svg");
    // 添加新的 svgs 规则
    config.module
      .rule("svg")
      .test(/\.(svg)(\?.*)?$/)
      .type("asset/inline");

    // 删除名为 images 的规则
    config.module.rules.delete("images");
    // 添加新的 images 规则
    config.module
      .rule("images")
      .test(/\.(png|jpe?g|gif|webp|avif)(\?.*)?$/)
      .type("asset/inline");

    // 删除名为 fonts 的规则
    config.module.rules.delete("fonts");
    // 添加新的 fonts 规则
    config.module
      .rule("fonts")
      .test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/i)
      .type("asset/inline");
  },
};
  • 其实这些图片/字体资源应该要上CDN,因为是内部项目,没有CDN资源申请,所以需要把所有图片/字体都打包进去包里,而且不是抽出来。可以去webpack官网对asset/inline的描述:webpack.js.cn/guides/asse...

后续

后续的需求是希望不同的项目都可以接入这个组件,所以后续需要考虑改造成web component,已知有以下问题需要解决:props传参失去了响应式,不支持appendbody所以要做对应的处理。

相关推荐
lee5763 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
摆烂式编程7 小时前
node.js 07.npm下包慢的问题与nrm的使用
前端·npm·node.js
东锋1.37 小时前
npm命令与yarn命令的区别
前端·npm·node.js
Amy_cx17 小时前
npm install安装缓慢或卡住不动
前端·npm·node.js
fechild2 天前
npm和webpack学习
学习·webpack·npm
程序员WANG4 天前
当使用 npm 时,出现 `certificate has expired` 错误通常意味着请求的证书已过期。
前端·npm·node.js
m0_748251524 天前
解决npm install安装出现packages are looking for funding run `npm fund` for details问题
前端·npm·node.js
网络点点滴4 天前
使用 Parcel 和 NPM 脚本进行打包
前端·npm·node.js
小黄编程快乐屋4 天前
npm操作大全:从入门到精通
前端·npm·node.js