Web Component的一些实践及思考

1,788 阅读4分钟

背景

随着业务发展和技术栈的演进,我们的技术栈也在随之变化。但是技术栈不统一,对于团队发展来说是不利的,所以当团队慢慢扩大,随之而来业务也会变多,技术栈收敛也势在必行。

最终我们团队选择以vue为主的技术栈,现有react技术栈项目也将会慢慢往vue上迁移,当然这个过程时比较漫长。在这个过程中,就面临一个问题,有很多业务场景一致,但封装成组件如何在多技术栈中同时使用?(vue、 react or other) 大家可能都想到了 web component, wc的确是最好的方式,同时官方也在积极推动各大浏览器尽快支持,有官方背书自然靠谱,至少不至于写出来的组件最后被废弃。

这篇文章主要介绍了如何使用vue开发wc组件,这也是我的一种尝试。思考在后面,如果想看结论可以直接拉到最后,这只是一种尝试。

操作步骤

1、使用vue-cli3创建项目

    vue create vue-web-component

2、我们创建一个组件: MyWebComponent.vue

<template>
  <div class="container">
    <h1>My Vue</h1>
    <div>{{ msg }}</div>
    <p @click="heandleClick">{{ message }}</p>
  </div>
</template>

<script>
  export default {
    props: ['msg'],
    data() {
      return {
        message: 'click me'
      }
    },
    methods: {
      heandleClick() {
        alert(1)
      }
    },
  }
</script>

<style scoped>
</style>

3、把组件改造成web component形式

3.1、 安装依赖 @vue/web-component-wrapper。 这个库可以将web component API,自动代理组件的属性、事件、和slot。
    yarn add @vue/web-component-wrapper -D
3.2、为了开发方便调试,我们需要改造下main.js文件, 并且在index.html文件中添加web component的引用形式。
// main.js中添加如下代码
    import wrap from '@vue/web-component-wrapper';
    import MyWebComponent from './components/MyWebComponent';
    const WrappedElement = wrap(Vue, MyWebComponent);
    window.customElements.define('my-web-component', WrappedElement);
    
// public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title>vue-web-component</title>
  </head>
  <body>
    <my-web-component msg="attribute says hi!"></my-web-component>
  </body>
</html>
3.3、运行项目
    yarn serve

这时,我们访问127.0.0.1:8080,就会看见这个界面.

3.4、编译成npm包

vue-cli3 内置了编译组件为web component的形式,我们指定编译target为wc即可。

"scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build --target wc --name my-web-component ./src/components/MyWebComponent.vue",
    "lint": "vue-cli-service lint"
  },

编译成功后,在dist目录下会生成demo.html 和my-web-component.js。

在demo.html文件中,可以看见通过web component的形式使用开发的组件。

<meta charset="utf-8">
<title>my-web-component demo</title>
<script src="https://unpkg.com/vue"></script>
<script src="./my-web-component.js"></script>

<my-web-component></my-web-component>
3.5、支持开发模式下调试样式

demo是展示了,但发现样式没有生效,这里其实是一个坑,好在强大的stackoverflow已经有人解决了,方法如下:

在项目的根目录下面创建vue.config.js文件,添加如下内容:

function enableShadowCss(config) {
  const configs = [
    config.module.rule('vue').use('vue-loader'),
    config.module.rule('css').oneOf('vue-modules').use('vue-style-loader'),
    config.module.rule('css').oneOf('vue').use('vue-style-loader'),
    config.module.rule('css').oneOf('normal-modules').use('vue-style-loader'),
    config.module.rule('css').oneOf('normal').use('vue-style-loader'),
    config.module.rule('postcss').oneOf('vue-modules').use('vue-style-loader'),
    config.module.rule('postcss').oneOf('vue').use('vue-style-loader'),
    config.module.rule('postcss').oneOf('normal-modules').use('vue-style-loader'),
    config.module.rule('postcss').oneOf('normal').use('vue-style-loader'),
    config.module.rule('scss').oneOf('vue-modules').use('vue-style-loader'),
    config.module.rule('scss').oneOf('vue').use('vue-style-loader'),
    config.module.rule('scss').oneOf('normal-modules').use('vue-style-loader'),
    config.module.rule('scss').oneOf('normal').use('vue-style-loader'),
    config.module.rule('sass').oneOf('vue-modules').use('vue-style-loader'),
    config.module.rule('sass').oneOf('vue').use('vue-style-loader'),
    config.module.rule('sass').oneOf('normal-modules').use('vue-style-loader'),
    config.module.rule('sass').oneOf('normal').use('vue-style-loader'),
    config.module.rule('less').oneOf('vue-modules').use('vue-style-loader'),
    config.module.rule('less').oneOf('vue').use('vue-style-loader'),
    config.module.rule('less').oneOf('normal-modules').use('vue-style-loader'),
    config.module.rule('less').oneOf('normal').use('vue-style-loader'),
    config.module.rule('stylus').oneOf('vue-modules').use('vue-style-loader'),
    config.module.rule('stylus').oneOf('vue').use('vue-style-loader'),
    config.module.rule('stylus').oneOf('normal-modules').use('vue-style-loader'),
    config.module.rule('stylus').oneOf('normal').use('vue-style-loader'),
  ];
  configs.forEach(c => c.tap(options => {
    options.shadowMode = true;
    return options;
  }));
}

module.exports = {
  // https://cli.vuejs.org/guide/webpack.html#chaining-advanced
  chainWebpack: config => {
    enableShadowCss(config);
  }
}

感想

虽然vue官方支持了web component的形式的写法, 但我还是不推荐这样去写。原因如下:

  • web component可以算是以后组件统一的趋势, 但是使用vue的形式难免很鸡肋,本来是wc是组件复用最好方式,但又使用了vue,如果只是因为他书写方便的话,这个理由可能不太充分。

  • 针对场景来说,如果一个react项目,引入了一个vue版本的wc,并且还需要把vue源码引入,一个项目中既有vue又有react,显得不伦不类。

综上,如果是跨技术栈组件复用场景,现阶段还是建议使用wc原生写法。 使用vue的形式,只是我的一种尝试,并不代表最佳写法。