zoukankan      html  css  js  c++  java
  • 2020-03-07:自以为比较好的微前端框架,还需要解决热打包和多版本依赖问题

    主要诉求和存在的问题

    • 这是一个包含多个模块的大型前端项目,过往我们采用多个单页面来隔离各个模块,现希望只使用一个单页面包含所有模块
    • 打包
      • 采用多个单页面是为了加快打包速度,使各个模块的发布互不影响。如何解决模块打包速度问题,热打包能否胜任?
      • 采用多个单页面时,公共组件改动必须对所有模块进行重新打包。在单页面应用中如何避免公共组件的修改导致所有模块重新打包?
        • @vue/cli创建的项目在打包时各个chunk之间是相互独立的,但是都共同会影响到app.js这个文件。即,一个文件改变只会影响到同步引用该文件的模块,不会影响到异步引用的。
      • 如何实现同一库不同版本的共存
      • @vue/cli创建的项目原生打包规律
        • 一个组件只在一个chunk中使用,被打包到chunk中
        • 在多个chunk中使用时,会根据组件的大小和关联的chunk包的多少自动判断是否需要打包成公共依赖
        • 在blund中出现的话,不管有没有在其他chunk出现都会被打包到app.js中
      • 问题:公共组件和函数在chunk中使用,如何强制打包到app.js中?采取强制归入blund后是否能加快打包速度?
    • 多语言
      • 在只有几种语言的项目中,把语言内嵌各个组件中比较方便代码管理
      • 但是它无法独立打包各类语言,无法实现按需加载的要求。当然可以开发webpack插件,用来处理 i18n 标签实现分语言集合,但这违背了基于组件的本地化本身的原理,而且还需要改动调用路径,这并不划算
      • 在需要多种语言时,为了压缩语言包体积,采用按需加载。同时为了方便管理散落在各个模块内的语言文件,还需要实现自动构建。
      • 更复杂的情况是按用户当前模块、倾向语言需要进行加载,但是这需要权衡多次请求的开销和语言包大小开销。
      • 最好的方式是实现服务端渲染 vue-i18n-extensions
    • 路由
      • 如何管理多个框架共存的路由
    • 状态
      • 如何实现sessionStorage和状态的同步
      • 如何跨框架共享状态

    目录结构

    - assets 静态文件目录
    - components 全局公共组件
        - langs 全局公共组件多语言
            - zh.json 
            - other.json
        - Container.vue (详)公共组件代码,这是一个容器组件用于路由嵌套,把所有功能点路由都平铺容易导致路由寻址缓慢。这里依据功能块来嵌套路由
        - other.vue
    - fun 全局公共函数
        - buildLang.js (详)构建i18n messages对象的函数
        - other.js
    - langs
        - zh.js 使用buildLang搜集散落在模块中的zh语言包,每种语言需要一个文件
        - other.js
    - modules
        - one 大功能模块
            - components 大功能模块中的公共组件
            - fun 大功能模块中的函数
            - views 大功能模块中的各个功能点
                - about 功能点about
                    - lang 功能点i18n
                        - zh.json
                        - other.json
                    - About.vue 功能点的代码
                    - Other.vue
                    - routes.js 功能点的路由
                - home 功能点home
                    - 同about
            - moduleRoutes.js 功能块路由
            - store.js 功能块状态,待商榷
        - other 其他大功能块
            - 同one
    - App.vue
    - i18n.js 按浏览器当前语言或用户配置加载langs文件中的多语言,并设置对应的其他语言相关项
    - main.js
    - router.js 收集各个功能块的路由生成根路由,并注册如vue实例
    - store.js 根状态,待商榷
    

    多语言

    • i18n.js 根据浏览器当前语言或用户保存在缓存中的语言按需加载,并设置语言相关项
    import Vue from "vue";
    import VueI18n from "vue-i18n";
    Vue.use(VueI18n);
    const loadedLanguages = []; // 已经加载过的语言
    function getNavigatorLanguage() {
      const lang = navigator.language;
      return lang.slice(0, 2);
    }
    export async function loadLanguage(lang) {
      if (i18n.locale !== lang) {
        if (!loadedLanguages.includes(lang)) {
          try {
            const msgs = await import(
              /* webpackChunkName: "lang-[request]" */
              `@/langs/${lang}`
            );
            i18n.setLocaleMessage(lang, msgs.default);
            loadedLanguages.push(lang);
            return setLanguage(lang);
          } catch (error) {
            return loadLanguage(
              process.env.VUE_APP_I18N_FALLBACK_LOCALE || "en"
            );
          }
        }
        return setLanguage(lang);
      }
      return lang;
    }
    function setLanguage(lang) {
      i18n.locale = lang;
      localStorage.setItem("lang", lang);
      // axios.defaults.headers.common["Accept-Language"] = lang;
      document.querySelector("html").setAttribute("lang", lang);
      return lang;
    }
    const i18n = new VueI18n({
      fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || "en",
      locale: "null"
    });
    loadLanguage(localStorage.getItem("lang") || getNavigatorLanguage());
    
    export default i18n;
    
    • en.js 收集语言包并组装
    import buildLang from "@/fun/buildLang.js";
    const langs = require.context("../", true, /en.json$/);
    export default buildLang(langs);
    
    • buildLang.js 组装语言包的方法
    // .env messages对象的结构是根据语言包路径生成的,但是很多路径片段没有意义,所以添加了过滤项常数。
    // 例如:src/modules/one/views/about/langs/zh.json 内的 {message:"一些消息"} 在使用时应该是$t('one.about.message')
    VUE_APP_I18N_MESSAGES_FILTER_PATHS=[ "modules", "views", "langs"]
    
    // buildLang.js
    const filterPaths = JSON.parse(process.env.VUE_APP_I18N_MESSAGES_FILTER_PATHS);
    function setMessages(pathArr, index, langValue, messagesPointer) {
      const path = pathArr[index];
      if (++index === pathArr.length - 1) {
        messagesPointer[path] = langValue;
      } else {
        if (!messagesPointer[path]) {
          messagesPointer[path] = {};
        }
        return setMessages(pathArr, index, langValue, messagesPointer[path]);
      }
    }
    export default function(langs) {
      const messages = {};
      langs.keys().forEach(key => {
        const pathArr = key.split("/").filter(path => {
          return !filterPaths.includes(path);
        });
        setMessages(pathArr, 1, langs(key), messages);
      });
      return messages;
    }
    

    路由

    收集散落在各个功能块的路由,生成路由表

    • router.js 构建路由,不是特别完善,需要增加对404的处理。
    import Vue from "vue";
    import VueRouter from "vue-router";
    
    Vue.use(VueRouter);
    const modulesRoutes = require.context("./modules", true, /moduleRoutes.js$/);
    const routes = [];
    modulesRoutes.keys().forEach(key => {
      routes.push(modulesRoutes(key).default);
    });
    const router = new VueRouter({
      mode: "history",
      base: process.env.BASE_URL,
      routes
    });
    
    export default router;
    
    • src/modules/one/moduleRoutes.js 功能块的根路由,由各个功能块自行维护。也可以自动生成,视情况而定
    import Container from "@/components/Container";
    
    const modulesRoutes = require.context("./views", true, /routes.js$/);
    const children = [];
    modulesRoutes.keys().forEach(key => {
      children.push(...modulesRoutes(key).default);
    });
    export default {
      path: "/one",
      component: Container,
      children
    };
    
    • src/modules/one/views/about/routes.js
    • 一个功能点一般由一人负责,这个功能点可能包含多个页面,它对外应该是一个数组。
    • 功能点得路由应该返回一个数组,这样可以支持平铺所有页面的方式,也可以采用嵌套的方式
    • 采用同步引用还是异步引用应当根据使用情况和代码大小决定,一般一个功能模块使用一个chunk就足够了
    • 路由相关的面包屑应该在meta中体现,而不是依赖路由嵌套关系
    // 采用嵌套的方式会使寻址更快
    const routes = [
        {
            path: `home`, // 功能块,功能块一般是依据业务点集合的,由负责的程序员自行定义
            component: HomeNav, // 该功能块拥有类似的结构
            children: [
              {
                path: "",
                name: "incHome",
                component: Home
              },
              {
                path: ":id",
                name: "incHomeDetail",
                component: HomeDetail,
                props: true
              }
            ]
        },
    ];
    
    export default routes;
    
    • Container.vue 容器组件,当路由需要依据功能块分层时用来包裹children
    export default {
      render() {
        return <router-view />;
      }
    };
    
    • .env 和src同级的环境配置文件
    VUE_APP_I18N_MESSAGES_FILTER_PATHS=["langs", "views", "modules"]
    VUE_APP_I18N_FALLBACK_LOCALE=en
    

    根据用户菜单权限动态生成路由

    • router.addRoutes 待补充
  • 相关阅读:
    重构之重新组织函数(ExTract Method)
    设计模式之桥接模式
    设计模式原则之里氏替换原则
    设计模式原则之依赖倒置原则
    设计模式原则之接口隔离原则
    设计模式原则之单一职责原则
    编译php5.6
    wireshark总结
    Blu-Ray BRRip 和 BDRip 的区别
    openwrt虚拟机的network unreachable
  • 原文地址:https://www.cnblogs.com/qq3279338858/p/12508756.html
Copyright © 2011-2022 走看看