【问题标题】:How to include vue app inside another vue app?如何将 vue 应用程序包含在另一个 vue 应用程序中?
【发布时间】:2020-09-06 00:15:17
【问题描述】:

我目前被要求生成将包含在不同客户网站中的小部件。

我们可以这样说:

有两个约束:

  1. 禁止使用 iframe
  2. 客户网站可以使用任何技术(PHP、React、AAngular、Vue.js、JQuery ......)

由于请求的小部件应该是交互式的,我想使用 javascript 框架开发它(我考虑过 Vue.js)。

但是,我认为这可能会导致一些冲突(甚至破坏客户的网站),例如如果客户的网站已经在使用另一个版本的 Vue.js。

我花了几个小时寻找和思考解决方案,但期望使用 iframe 我没有找到任何东西......

=> 有没有办法将 Vue.js 应用程序包含在另一个 Vue.js 应用程序(或 React、Angular 等)中?

提前感谢您的帮助。

【问题讨论】:

    标签: javascript vue.js vuejs2


    【解决方案1】:

    我目前的公司通常在我们客户的网页中提供嵌入式应用程序。 注意:虽然下面的例子是用.ts写的,但在.js几乎是一样的。

    在测试了各种方法(Vue 相当灵活)之后,我们最终做了以下工作:

    该方法的灵感来自mapbox-gl的模型,适用于Vue。
    基本原理很简单:将整个应用导出为干净的 JS 类(通过单个脚本导入)并运行

    new SomeClass(selector, config);
    

    为此,我通常会创建一个someClass.ts 文件(它通常会完成main.ts 的工作),但会导出一个类:

    import Vue from 'vue';
    import App from './App.vue';
    import store from './store';
    import i18n from '@/plugins/i18n';
    import VA from 'vue-axios';
    import axios from 'axios';
    import store from './store';
    ...
    
    Vue.use(VA, axios);
    Vue.config.productionTip = false;
    
    const Instance = new Vue({
      store,
      i18n,
      components: { App },
      data: () => ({ config: {} }),
      render: function(createElement) {
        return createElement('app', {
          props: { config: this.config }
        });
      }
    });
    
    export default class SomeClass {
      constructor(selector: string, config: SomeClassConfig) {
        Vue.set(Instance, 'config', {
          ...config,
          // id is optional. but allows setting the app's `id` dynamically to same `id`
          // as the context page placeholder (which app replaces), for consistency
          // for this, you have to assign `config.id` to App's `$el` in its `mounted()`
          id: selector.replace('#', '')
          // or document.querySelector(selector).id || 'someClassApp'
        });
        Instance.$mount(selector);
        return Instance;
      }
    }
    
    // optional exports, used for type inheritance throughout the app
    export const { $http, $t, $store } = Instance;
    

    注意:明确这一点:没有上述导入是必需的(即:axiosi18n 等... - 这只是一个例子)。做你的main.ts(或main.js)通常做的任何事情。然后创建Instance,注入配置,从Instance 导出您可能需要的类和任何命名导出。

    上面已经完成了工作(如果您使用下面指定的build 命令构建它),但您还必须调整main.ts 以导入此类并在服务时呈现应用程序:

    import SomeClass from '@/someClass';
    
    export default new SomeClass('#devhook', {
     // config object
    });
    
    const app = document.querySelector('#devhook');
    if (app instanceof HTMLElement) {
      app.addEventListener('some-event', function(e) {
        if (e instanceof CustomEvent) {
          console.log(e.detail);
          // you can test events emitted for context app here...
        }
      });
    }
    

    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="./favicon.ico">
        <!-- VueJS -->
        <script src="https://cdn.jsdelivr.net/npm/vue@latest/dist/vue.min.js"></script>
        <title><%= htmlWebpackPlugin.options.title %></title>
      </head>
      <body>
        <noscript>
          <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
        </noscript>
        <div id="devhook"></div>
        <!-- built files will be auto injected -->
      </body>
    </html>
    

    构建命令是:

    vue-cli-service build --target lib --name SomeClass --inline-css --inline-vue src/someClass.ts
    

    请注意,我们正在导出 src/someClass.ts。您最终会得到一个名为 SomeClass.umd.js 的文件。

    另外,将此设置添加到config.vue.js

    configureWebpack: {
      output: {
        libraryExport: 'default'
      }
    }
    

    当您在上下文页面中导入它时,将使您能够在没有.default() 的情况下初始化该类。如果没有上面的 webpack 选项,你需要像这样初始化它:

    new Someclass.default(selector, config);
    

    其他一切都很标准。

    而现在上下文页面只需要导入.umd.js,使用实例化app

    new SomeClass(selector, config)
    

    ...并在呈现的元素上添加一个或多个侦听器。

    .umd.js 导出可以从外部服务器提供服务,并且显然可以从 npm 包中导入。

    如果单独加载它们更有意义(即:Vue 可能已经存在于上下文页面中),您不必--inline-css--inline-vue。另外,我经常依赖上下文页面中已经存在的库(您希望尽可能多地重用库),使用vue.config.js

    configureWebpack: {
      externals: {
        lodash: 'window._'
      }
    }
    

    执行此操作时,请记住在 public/index.html 中添加指向 lodash/jquery/whatever cdn 的脚本,以便在服务/开发时加载它,就像在上下文页面中一样。

    最后,demo.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">
        <title>Some Class App Demo</title>
        <link rel="shortcut icon" href="./favicon.ico"/>
    
        <!-- also works when served from a different domain -->
        <script src="./SomeClass.umd.min.js"></script>
        <link rel="stylesheet" href="./SomeClass.css">
    
    </head>
    <body>
    <div id="SomeClassApp"></div>
    
    <script>
      const config = {
        // whatever
      };
      const app = new SomeClass('#SomeClassApp', config);
    
      if (app.$el instanceof HTMLElement) {
        app.$el.addEventListener('some-event', function(e) {
          if (e instanceof CustomEvent) {
            console.log(e.detail);
          }
        });
      }
    </script>
    </body>
    

    当然,您必须在应用脚本 (./SomeClass.umd.min.js) 之前添加所有外部组件(如果您使用了任何组件)。不要defer他们。该应用程序希望它们在启动时加载到window

    至于冲突,你不应该有任何冲突。 Vue 在未成年人之间高度兼容,因此您可以简单地使用上下文页面的 Vue(而不是内联它)。此外,Vue 2 语法应该与 Vue 3 完全兼容(例如,您可以使用 Vue 3 运行 Vue 2 应用程序而无需对其进行任何更改 - 我们将拭目以待)。

    【讨论】:

    • 这真的很有帮助@tao - 我是一个完全的 Vue 新手,但需要在 Vue 3 中做这种事情。同样的模式是否有效?
    • @Richard,是的,同样的模式肯定适用于 Vue 3,但实现细节会有所不同。据我估计,结果实际上会更干净。换一种说法,尽管尝试它不会有什么坏处,如果你真的刚开始使用 Vue,在 Vue 3 中尝试上述方法意味着你不会有太多的例子可以遵循,所以你必须依靠你的能力深入了解 Vue 的源代码,而在 Vue 2 中,您会找到插件、示例和帮助程序。
    • @tao 你能帮忙看看没有 --inline-vue 的例子吗?我能够通过 --inline-vue 实现。我收到错误:Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'render') at Object.app.mount
    【解决方案2】:

    你可以简单地使用 JS 来解决。我已经完成了类似的任务,将我的 Vue 小部件附加到任何其他网站。这是您可以执行的示例代码:

       const appendWidget = () => new Promise((resolve, reject) => {
           let e = document.createElement("div")
           e.setAttribute("id", "widgetToAppend");
           document.body.appendChild(e);
           let app = new Vue({
             el: "#widgetToAppend",
             template: `<div>YOUR COMPONENTS</div>`
           })
           resolve(app);
        })
    

    【讨论】:

    • 最终用户如何通过脚本标签添加上述内容?您仍然需要脚本文件中的 vue 实例。
    【解决方案3】:

    我怀疑,如果版本不同(客户端使用 Vue 2.5,您使用 2.6),您很可能会遇到问题。

    我过去在使用 React 和 Vue 时遇到过这种情况。

    这可能是我在我知道该项目将用作依赖项/小部件的情况下使用Svelte 的#1 原因。

    如果您习惯了 Vue,您应该很快就会有宾至如归的感觉。显着的区别在于,从技术上讲,它是一种语言,而不是框架,它从 dist 包中编译出框架,因此您没有框架依赖项。

    【讨论】:

    • 这看起来很有希望,感谢您的反馈。我去看看
    • 另外请记住,Vue 3 将在今年晚些时候公开发布。一旦它被大量采用,这可能会进一步加剧具有框架依赖关系的问题。当 Svelte 从 v2 升级到 v3 时,我没有遇到并排运行小部件的问题。
    • 再次感谢您的反馈,但是,有些东西我应该错过。我看了一下这个 svetle 示例:svelte.dev/examples#hello-world 并且根据 JS 输出,仍然存在 svetle 依赖项(从“svelte/internal”导入 {SvelteComponent})。那么如果客户网站使用不同版本的 svetle 呢?还是可能有一些冲突?我错了吗?
    • 这是内部代码(有点混乱,不知道他们为什么称它为输出)。编译后的 svelte 代码将没有任何 svelte 依赖项。这是我制作的 svelte 包中指向 dist 目录的链接。 github.com/dasDaniel/svelte-table/tree/master/dist 生成的包都没有使用 svelte 依赖项。
    • 好的,再次感谢您花时间回答我的问题
    猜你喜欢
    • 1970-01-01
    • 2019-07-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-17
    • 2021-11-08
    • 1970-01-01
    相关资源
    最近更新 更多