Vue渲染远端组件的方法

93 阅读1分钟

Vue渲染远端组件的方法

在项目中,我们有时候需要按不同条件,渲染不同的组件,把这些组件都打包到项目中,会影响包的大小,修改组件后也需要上线整个项目,对开发、上线、维护都不利。

我们利用Vueis属性,去加载一个动态组件

首先我们需要把组件打包,放到服务器

/** 打包remote components */
const webpack = require('webpack');
const path = require('path');
const utils = require('./utils');
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

let componentNames = [] //远端组件名 array
let param = process.argv[2] // npm run buildRmotes remote-xfnj  | param = remote-xfnj
let prePath = '/src/insurances/'
const componentsObj = {}
console.log(process.argv)
// let arr = require('../src/structure/'+param+'.json')
// console.log(arr)
if (param) {
  componentNames = require('../src/asyncBuildConfig/' + param + '.json')
} else {
  componentNames = require('../src/asyncBuildConfig/index.js')
}
console.log(componentNames)
function resolve (dir) {
  return path.join(__dirname, '..', dir)
}
if (componentNames.length <= 0) {
  return
}
componentNames.map((val) => {
  componentsObj[val.name] = resolve(prePath + val.path + '.vue')
})

module.exports = {
  entry: {
    // componentA: resolve('/src/views/component-a.vue'),
    ...componentsObj
    // componentB: resolve('/src/views/component.vue')
  },
  output: {
    path: resolve('/main/'),
    filename: '[name].min.js',
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
    }
  },
  externals: [ //相关依赖注明,防止重复打包
    'vue',
    {
      // lodash: {
      //   commonjs: 'lodash',
      //   commonjs2: 'lodash',
      //   amd: 'lodash',
      //   root: '_', // 指向全局变量
      // }
      lodash: '_'
    },
    // './jTool': 'jTool',
    // '@/util/jTool': 'jTool',
    function (context, request, callback) {
      // if (/common(\.js)?$/.test(request)) {
      //   console.log(context, '===context====');
      //   console.log(request, '===request===');
      // }
      if (/eventBus(\.js)?$/.test(request)) {
        return callback(null, 'eventBus');
      }
      if (/jTool(\.js)?$/.test(request)) {
        console.log(request, '===========request=======')
        return callback(null, 'jTool');
      }
      callback();
    }
  ],
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          esModule: false, // vue-loader v13 更新 默认值为 true v12及之前版本为 false, 此项配置影响 vue 自身异步组件写法以及 webpack 打包结果
          loaders: utils.cssLoaders({
            sourceMap: false,
            extract: false          // css 不做提取
          }),
          transformToRequire: {
            video: 'src',
            source: 'src',
            img: 'src',
            image: 'xlink:href'
          }
        }
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include: [resolve('src'), resolve('test')]
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('media/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [
    // new webpack.DefinePlugin({
    //   'process.env.NODE_ENV': '"production"'
    // }),
    new webpack.LoaderOptionsPlugin({
      vue: {
        postcss: [require('postcss-pxtorem')({ rootValue: 40, propWhiteList: [] })]
      }
    }),
    // UglifyJs do not support ES6+, you can also use babel-minify for better treeshaking: https://github.com/babel/minify
    new webpack.optimize.UglifyJsPlugin({
      compress: false,
      sourceMap: false
    }),
    // Compress extracted CSS. We are using this plugin so that possible
    // duplicated CSS from different components can be deduped.
    new OptimizeCSSPlugin({
      cssProcessorOptions: {
        safe: true
      }
    }),
    new BundleAnalyzerPlugin()
  ]
};

Axios 获取到组件,new Function的方式转换成Vue组件,通过 is 属性渲染。

<template>
  <component
    :is="mode"
    v-bind="$attrs"
    v-on="$listeners">
  </component>
</template>

<script>
  import Axios from 'axios';

  export default {
    name: 'AsyncComponent',
    props: {
      // 父组件提供请求地址
      url: {
        type: String,
        default: ''
      }
    },
    inheritAttrs: true,
    data() {
      return {
        resData: '',
        mode: ''
      };
    },
    watch: {
      url: {
        immediate: true,
        async handler(val, oldVal) {
          if (!this.url) return;
          // Cache 缓存 根据 url 参数
          if (!window.SyncComponentCache) {
            window.SyncComponentCache = {};
          }
          let res;
          if (!window.SyncComponentCache[this.url]) {
            window.SyncComponentCache[this.url] = Axios.get(this.url);
            res = await window.SyncComponentCache[this.url];
          } else {
            res = await window.SyncComponentCache[this.url];
          }
          let Fn = Function;
          this.mode = new Fn(`return ${res.data}`)();
        }
      }
    }
  };
</script>

也可以添加一些钩子函数,用于资源加载完后的回调。

需要注意的是,远端组件不要去加载公共的代码,用 externals 排除掉。