单个vue组件的打包和动态引入

一、原理简介

在使用webpack打包vue项目时,我们一般习惯以项目的main.js为打包入口,构建完整的项目依赖。依赖构建完毕后将打包后的js通过插件html-webapck-plugin引入到HTML模板中,从而实现整个项目的打包。

由于webpack运行于nodejs环境下,只能识别js文件,因此如果遇到其他类型的文件,如.vue,就必须使用对应的loader先转化成js文件。Vue提供了vue-loader来将vue单页面组件转化成js代码(vue-loader本身依赖vue-template-compiler来解析模板,如果组件包含样式,一般还需要引入style-loadercss-loader)。

可能很多人会有一个错觉,是不是webpack只能以js文件为入口?

当然不是这样的!只要有loader的支持,.vue文件同样可以作为打包入口。这也是我们打包单个vue组件的基本思路。下面我们来看具体的打包过程。

二、打包过程

1. 创建一个webpack项目

这个非常简单,首先安装webpack和webpack-cli(高版本webpack依赖webpack-cli):

npm install webpack webpack-cli -g

然后进入要建项目的目录,新建一个文件夹(如vue-pack),打开命令行输入以下指令初始化一个项目:

npm init

一路回车,最终会在该路径下自动生成一个package.json,这是项目描述文件。由于当前项目还没有安装任何依赖,所以package.jsondependencies字段还不存在。所以接下来我们要在当前项目中安装打包单个vue组件所需要的包(高版本的vue-loader需要依赖vue-loader-plugin插件,否则打包的时候会报错):

npm install vue-loader vue-loader-plugin vue-template-compiler vue-style-loader css-loader --save

回车,等待安装完成。现在项目下应该会增加一个node_modules文件夹,这是第三方node包目录。

由于webpack4不再默认生成配置文件,因此我们可以在当前路径下新建一个webpack.config.js文件作为打包的配置文件。

2. 编写组件和打包脚本

由于我们只是简单演示单组件的打包,这里以一个简单的Vue组件为例。我们在当前目录下新建src/HelloWorld.vue,然后编写简单的vue组件:

<template>
  <div>{{ name }}</div>
</template>

<script>
export default {
  data() {
    return {
      name: 'carter'
    }
  }
}
</script>

<style>
  div {
    color: red;
  }
</style>

现在最关键的是如何配置打包脚本,我们来看webpack.config.js的编写:
webpack.config.js

const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
    entry: './src/HelloWorld.vue',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'helloWorld.js',
        library: 'helloWorld'
    },
    plugins: [
        new VueLoaderPlugin()
    ],
    module: {
        rules: [{//vue 解析
            test: /\.vue$/,
            loader: 'vue-loader',
        },
        {//css 解析
            test: /\.css$/,
            use: ['vue-style-loader', 'css-loader']
        }]
    }
}

我们来解释上述配置。

首先,entry字段定义了打包入口为src路径下的HelloWorld.vue,也就是我们将要打包的vue组件。这里的library字段我们在后面会解释。

然后,output定义了打包出口,这里我们会把打包结果输出到当前路径下的dist文件夹,打包结果名为helloWorld.js

随后,我们在plugins中引入了VueLoaderPlugin,这是vue-loader所需的依赖。

最后,我们对不同的资源配置loader。对于.vue后缀的,我们使用vue-loader解析,对于样式,我们使用vue-style-loadercss-loader进行解析。

现在我们来解释一下entry字段的library值的含义。

添加library字段表明这个入口文件打包之后会作为一个第三方库使用,而不是会被插件嵌入到HTML模板。一般来说,文件在打包完成之后不会向外输出变量,而是得到一个立即执行的函数,如:

!(function(){ ... })()

这样的打包结果会被html-template-compiler嵌入到模板中调用:

...
<script src="/js/bundle.js"></script>
...

由于我们的打包入口一般都是main.js,它内部包含new Vue这样的代码,因此由它打包出的js具有自动挂载和初始化的能力,我们只需简单引入它,就可以构建整个vue应用。

但是单vue组件不同,它只是第三方包,没有new Vue这样的语句,不具备自动挂载的能力,需要在引入到项目后手动注册和挂载。因此单vue组件的输出结果必须是一个函数或对象。指定了library字段后,打包结果就变成了:

var helloWorld=function(e){...}; 

这个函数严格来讲是一个Module对象,它有一个default属性,保存了组件配置。现在你就可以通过变量helloWorld来执行组件的注册和挂载了,这个我们第三部分会介绍。

3. 打包

编写完脚本之后,打包就很容易了。我们去package.json里配置打包脚本:

{
  ...
  "scripts": {
    ...
    "build": "webpack",
  },
  ...
}

现在打开命令行,输入npm run build,你就会发现当前路径下多了一个dist文件夹,里面的helloWorld.js就是我们的打包结果。

三、动态引入

1. 在vue项目中使用

现在假设我们另外有一个项目,需要用到上述组件。而上述打包结果现在已经被保存在当前项目的public文件夹下,我们现在需要在App.vue中动态加载这个js,然后注册和使用组件。如:

public
  |- js
    |- helloWorld.js
src
  |- App.vue
  |- main.js

App.vue可能会这样写:

<template>
  <div :is="compName"></div>
<template>

<script>
  import Vue from 'vue';
  export default {
    data() {
      return {
        compName: ''
      }
    },
    mounted() {
      ...
    }
  }
</script>

我们的目标是在mounted生命周期中挂载这个第三方组件。假设我们还不知道要加载的第三方组件的名字(这里指的其实是打包时的library字段)和路径,我们可能需要先发送一个请求来获取:

mounted () {
  this.$axios.get('/getLibraryNameAndUrl').then(res => {
    let compName = res.libraryName;
    let url = res.url;
    ...
  })
}

接下来需要动态加载js,并在加载完毕后动态注册它:

mounted () {
  this.$axios.get('/getLibraryNameAndUrl').then(res => {
    let compName = res.libraryName;
    let url = res.url;
    
    let script = document.createElement('script');
    script.src = url;  // '/js/helloWorld.js'
    script.onload = () => {
      Vue.component('hello-world', window[compName].default);
      this.compName = 'hello-world';
    }
    document.body.appendChild(script);
  })
}

现在页面上应该可以渲染出引入的第三方组件了。我们看看这里做了什么。

首先,我们动态创建了一个script标签,并将url设置为第三方helloWorld.js的地址。然后我们注册了script标签的onload事件。我们第二部分已经讲到,helloWorld.js返回的实际上是下面的代码:

var helloWorld= function(e){...};

也就是说,当脚本执行完毕时,window对象上应该会新增一个变量:window.helloWorld。它的值是一个函数,同时它也是一个Module类型的对象。在浏览器输出helloWorld这个变量:
在这里插入图片描述
它的default属性就是我们要拿来进行注册组件的对象!我们通过Vue.component动态注册一个名为hello-world的组件(这里也可以选择动态从后台获取,或其他约定好的组件名),传入上述变量的default属性,即可完成注册。

随后我们只需要写this.compName = 'hello-world',template中的div就会被渲染为组件<hello-world></helloWorld>,第三方组件也就渲染完毕。

2. 在普通项目中使用

实际上在普通项目中也是一样的用法。

比如我们有一个普通的HTML页面,它引入了vue的cdn库。如果只是静态引入这个组件,那么可以这样写:

...
<div id="app"></div>
...
<script src="/js/helloWorld.js"></script>
<script>
  var app = new Vue({
		el: "#app",
		components: {helloApp: helloWorld.default},
		render: h => h('helloApp')
	})
</script>

如果需要动态加载,那么完全参照vue中的代码,使用document.createElement动态创建脚本标签并进行注册。

总结

实际上上述的打包过程完全可以使用node服务自动化完成,并且可以动态指定组件名,以防止多个组件出现打包冲突。比如我们可以通过nodejs提供的命令行模块:node-cmd,自动执行webpack命令。如果需要指定library字段,可以这样动态拼写:'webpack --output-library' + libraryName

所以你完全可以在服务端构建一个node服务,以单vue组件作为输入,自动执行打包命令,然后输出打包结果,再引入其他项目中。关于这套方案,目前本人仍在实践中…

猜你喜欢

转载自blog.csdn.net/qq_41694291/article/details/106453841