Vue-cli 4.x 组件库开发遇到哪些事
Init
当用脚手架命令vue create project-component
生成项目的时候,它的结构如下:
.
├── project-component
│ └── node_modules
│ ├── public
│ ├── src
│ ├── babel.config.js
│ ├── package.json
│ └── README.md
首先创建一个packages
目录,用来存放组件
然后将src
目录改为examples
用作示例
.
├── project-component
│ └── examples
│ ├── node_modules
│ ├── packages
│ ├── public
│ ├── babel.config.js
│ ├── package.json
│ └── README.md
修改配置
启动项目的时候,默认入口文件是 src/main.js
将 src 目录改为 examples 之后,就需要重新配置入口文件
在根目录下创建一个 vue.config.js 文件
// vue.config.js
module.exports = {
// 将 examples 目录添加为新的页面
pages: {
index: {
// page 的入口
entry: 'examples/main.js',
// 模板来源
template: 'public/index.html',
// 输出文件名
filename: 'index.html'
}
}
}
Vue-cli 提供了构建库的命令,所以这里不需要再为packages目录配置webapck。
开发组件
在packages
目录存放组件,该目录下存放每个组件单独的开发目录,和一个 index.js 整合所有组件,并对外导出,每个组件都应该归类于单独的目录下,包含其组件源码目录 src,和 index.js 便于外部引用
需要注意的是,组件必须声明 name,这个 name 就是组件的标签
import DrReading from '@/views/home.vue'
DrReading.install = function (Vue) {
Vue.component(DrReading.name, DrReading)
}
export default DrReading
单个组件导出后,在packages/index.js
统一实现全局注册
// 导入单个组件
import DrReading from './views/index'
// 以数组的结构保存组件,便于遍历
const components = [
DrReading
]
// 定义 install 方法
const install = function (Vue) {
// if (install.installed) return
// install.installed = true
// 遍历并注册全局组件
components.map(component => {
console.log(component)
Vue.component(component.name, component)
})
}
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
export default {
// 导出的对象必须具备一个 install 方法
install,
// 组件列表
...components
}
到这里组件就已经开发完毕
可以在 examples/main.js 中引入该组件
// examples/main.js
import DwReading from '../packages/index.js'
Vue.use(DwReading)
// App.vue
<dr-reading />
打包组件
vue-cli 提供了一个库文件打包命令
主要需要四个参数:
\1. target: 默认为构建应用,改为 lib 即可启用构建库模式
\2. name: 输出文件名
- dest: 输出目录,默认为 dist,这里我们改为 lib
\4. entry: 入口文件路径,默认为 src/App.vue,这里改为 packages/index.js
基于此,在 package.json 里的 scripts 添加一个 lib 命令
{
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"lib": "vue-cli-service build --target lib --name dr-reading --dest lib packages/index.js",
"release": "npm run lib && npm publish"
},
...
"files": [
"lib"
],
"main": "./lib/dr-reading.umd.min.js"
}
发布
在 package.json 添加组件信息,然后创建**.npmignore** 文件,设置忽略文件
该文件的语法和 .gitignore 的语法一样,设置发布到 npm 时忽略哪些目录或文件
.DS_Store
node_modules/
examples/
packages/
public/
vue.config.js
babel.config.js
*.map
*.html
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
创建 .npmrc
,设置发布地址
registry=http://registry.npmjs.org // 或者私库
如果是发布到npm,需要通过npm login登录或者到通过 npm adduser创建账号,具体流程可参考官方文档。
最后执行:
npm publish
发布失败
我发布到私库的时候,报了以下问题
413 Payload Too Large - PUT http://xxx- request entity too large
查到的解决方案
此时找到verdaccio的config.yaml,
如: /root/.config/verdaccio/config.yaml
修改 max_body_size: 100mb
重启verdaccio 就可以解决
Npm link
主要是平时开发时,有npm包需要在本地调试好了再发布。发一版测一版,或者把代码复制粘贴到项目文件夹里去调试,很不优雅。软链就变得极为有用了,特别是需要调试的npm包不止一个,且彼此之间需要联调。
使用
我想想在项目B里边用项目A的本地npm包。
先在对应npm包的文件创建一个全局的链接
cd ~/projects/projectA
npm link
然后再想要使用该包的项目里使用这个软链
注意这里的packageName一定要对应你的npm包package.json里的name字段值。
cd ~/projects/projectB
npm link packageName
npm unlink packageName // 使用npm包的项目的文件目录下解除特定的链接
npm unlink // 在npm包所在的文件目录下去除全局链接
Runtime Error integrating a component lib that uses @vue/composition-api: 'You must use this function within the "setup()" method'
The Actual Problem
在打包好组件库引入到项目中时,控制台报了一连串错误:
Runtime Error integrating a component lib that uses @vue/composition-api:
'You must use this function within the "setup()" method'
当看到这个问题,我也是一脸懵逼,主项目里也引入了 @vue/composition-api ,也能正常使用,为什么到了这个组件引入就会报这么个错误。
尝试了各种方法,发现还是出组件库里的 @vue/composition-api ,我把组件库清理干净重新打个包引入,能正常运行,恢复到 @vue/composition-api 引入就异常。
确定了问题所在就开始寻找解决方法。
Solution
经过在stackoverflow上有人遇到跟我一样的问题:
Runtime Error integrating a component lib that uses @vue/composition-api: 'You must use this functio
有大佬给出了问题产生原因及解决方法。
The child-component-lib was bundling their own versions of the npm packages
@vue/composition-api
andvuex-composition-helpers
. This had the following effect: When I was running the parent-app there were actually two instances of those libraries and the vue component from the child-component-lib was accessing the wrong object that had not been properly initialized.
这是由于子组件库捆绑了他们自己版本的 npm 包 @vue/composition-api 和 vuex-composition-helpers。这产生了以下影响:当我运行父应用程序时,实际上有两个库实例,而子组件库中的vue组件正在访问没有正确初始化的错误对象。
解决方案是通过以下方法防止在子组件库中捆绑这些库:
在
devDependencies
和peerDependencies
中引入它们;并且在构建的时候使webpack不要打包它们。
package.json
"dependencies": {
...
},
"devDependencies": {"@vue/composition-api": "^1.0.0-beta.19","vuex-composition-helpers": "^1.0.21",
...
},
"peerDependencies": {"@vue/composition-api": "^1.0.0-beta.19","vuex-composition-helpers": "^1.0.21"
},
vue.config.js
configureWebpack: {
externals: {
"@vue/composition-api": "@vue/composition-api",
"vuex-composition-helpers": "vuex-composition-helpers"
},
...
}
peerDependencies
是什么东西,这里为什么需要它?
初试 peerDependencies
为了搞懂上述问题是怎么解决,特意去查了下peerDependencies
是什么东西,在此记录一下。
example
假设现在有一个 helloWorld 工程,已经在其 package.json 的 dependencies
中声明了 packageA,有两个插件 plugin1 和 plugin2 他们也依赖 packageA,如果在插件中使用 dependencies
而不是 peerDependencies
来声明 packageA,那么 $ npm install
安装完 plugin1 和 plugin2 之后的依赖图是这样的:
.
├── helloWorld
│ └── node_modules
│ ├── packageA
│ ├── plugin1
│ │ └── nodule_modules
│ │ └── packageA
│ └── plugin2
│ │ └── nodule_modules
│ │ └── packageA
从上面的依赖图可以看出,helloWorld 本身已经安装了一次packageA,但是因为因为在 plugin1 和 plugin2 中的 dependencies
也声明了 packageA,所以最后 packageA 会被安装三次,有两次安装是冗余的。
而 peerDependency
就可以避免类似的核心依赖库被重复下载的问题。
如果在 plugin1 和 plugin2 的 package.json 中使用 peerDependency
来声明核心依赖库,例如:
plugin1/package.json
{
"peerDependencies": {
"packageA": "1.0.1"
}
}
plugin2/package.json
{
"peerDependencies": {
"packageA": "1.0.1"
}
}
在主系统中声明一下 packageA:
helloWorld/package.json
{
"dependencies": {
"packageA": "1.0.1"
}
}
此时在主系统中执行 $ npm install
生成的依赖图就是这样的:
.
├── helloWorld
│ └── node_modules
│ ├── packageA
│ ├── plugin1
│ └── plugin2
可以看到这时候生成的依赖图是扁平的,packageA 也只会被安装一次。
因此我们总结下在插件使用 dependencies
声明依赖库的特点:
如果用户显式依赖了核心库,则可以忽略各插件的
peerDependency
声明;如果用户没有显式依赖核心库,则按照插件
peerDependencies
中声明的版本将库安装到项目根目录中;当用户依赖的版本、各插件依赖的版本之间不相互兼容,会报错让用户自行修复;
又双叒叕 Composition-API
在这个组件库正式上线之后,我寻思写个使用文档,并且做一个demo贴上,以供组内同学们参考一下。然鹅刚在一个 vue create demo
的新项目引入就出现了问题。
这个项目很干净,干净到只新增了两个依赖。
"dependencies": {
"core-js": "^3.6.5",
"vue": "^2.6.11"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.6",
"@vue/cli-plugin-eslint": "~4.5.6",
"@vue/cli-service": "~4.5.6",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"vue-template-compiler": "^2.6.11",
"@vue/composition-api": "^1.6.2",
"axios": "^0.26.1"
},
我下意识是因为 @vue/composition-api
版本不一致导致的,我就把所有的引入版本跟组件库保持一致,但还是引入异常。
经过控制变量法以及一系列匪夷所思的操作下,我终于找到了原因。
"^"
在package-lock里我查看到,vue下载的版本是2.7.x,原因是没锁死版本,"vue": "^2.6.11"
,会自动下载vue2大版本下最新的库,vue2.7.x内置composition-api
,所以导致了该问题,离离原上谱。
webpack编译及其他问题
这里主要是由于webapck配置问题,出现的结果不如预期
丢失css样式
手动导入构建后的css文件,也可以构建时通过在 vue.config.js 中设置 css: { extract: false }
强制内联
// vue.config.js
module.export = {
...
css: { extract: false },
}
图片没有转成base64
由于需要最后生成的组件库只是一个js文件的话,就需要保证库的“干净”
,类似assets
和static
里的文件都要转成base64引入。
vue-webpack
模板的默认设置限制了转码的文件大小为10000B
以下
// vue.config.js
module.export = {
...
chainWebpack: (config) => {
config.module
.rule("images")
.use("url-loader")
.loader("url-loader")
.options({
limit: 1024 * 10,
fallback: {
loader: "file-loader",
options: {
name: "img/[name].[hash:8].[ext]",
publicPath,
},
},
})
.end();
}
}
vuex数据初始化失败
排查后发现没有引入vuex依赖,导致所有store中存储的数据全部为undfined
在构建 Web Components 组件或库时,入口点不是 main.js ,而是 entry-wc.js 文件,因此,要在 Web Components 组件的目标中使用 vuex ,你需要在 App.vue 中初始化存储 (store):
import store from './store'
// ...
export default {
store,
name: 'App',
// ...
}