cover_image

京东快递H5项目接入vite实战

彭博 京东技术
2023年03月15日 11:00


图片
Tech

导读

      本文介绍了如何在开发阶段将vite应用于vue 2.x 工程,从而提高研发的开发体验与效率。主要涉及如何兼容process变量,如何处理 node-sass 与 dart-sass冲突,以及路径别名的兼容处理等。通过这篇文章可以为读者在vite接入过程中遇到的问题提供一些解决方案, 并帮助读者理清vue工程接入vite的具体思路。本文主要从整体介绍了新版会员徽章系统的设计方案以及未来规划,主要描述了等级模型的设计思路,读者可以通过本文对徽章系统的核心功能有初步的了解。




01 
前言


在今年的敏捷团队建设中,我通过Suite执行器实现了一键自动化单元测试。Juint除了Suite执行器还有哪些执行器呢?由此我的Runner探索之旅开始了!

       随着H5 项目迭代,项目的启动时长在慢慢增长,目前H5的首次启动时长约为 1分钟;且文件的更新也可能触发大范围的依赖重新打包。vite、snowpack等bundless类型的打包工具的出现就是为了解决这个问题。本文将结合实际项目(京东快递H5)实现 vite 打包工具的无痛接入。由于目前未考虑在正式环境中使用vite进行构建,因此接入过程中需要考虑与现有打包方式的兼容问题。



02 
  徽章产品体系  


理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。     

     首先解决vite 需要的项目依赖,主要需要添加的项目依赖如下列出:

  • vite

  • vite-plugin-vue2 :官方提供的vite插件,用于兼容 vue2打包

  • @vitejs/plugin-legacy: 用于配置需要适配的低版本浏览器

  • vite-plugin-html: "^2.0.7":用于在模板文件中注入代码,注意版本 高版本可能需要更改

  • vue-template-compiler:vue单文件组件编译插件,要跟 vue版本一致

  • @rollup/plugin-babel: babel 相关配置

  • sass:css预处理语言所需基础库





03 
  模板文件index.html  


理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。
1.相比 vue-cli构建的项目,模板文件的位置需要更改,为了同时兼容 vue-cli 打包与vite打包,因此需要在根目录下新增 index.html。
2.模板文件需要主动导入项目入口文件 main.js/ts
【HTML/XML】<script type="module" src="/src/main.js"></script>


04 
  项目启动问题  


理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目1. 版简易的数据看板,包括业务线的人数概览、本月发放权益数量、权益每日变化趋势
1. vue 中 /deep/ 方式覆盖深层组件样式的方式不可用,需要替换为 ::v-deep;
2.所有的单文件组件导入必须包含 .vue 扩展;
3.style 中 通过 ~@ 方式书写的路径需要额外的通过 resolve.alias 设置路径别名。
【Javascript】     '~@': resolve(__dirname, 'src')
4.提示 global 不存在,需要做兼容处理,通过模板文件(index.html)在全局添加 global,当然也可以通过vite的插件(vite-plugin-global-polyfill)实现 global 变量的兼容,使用方式可参考源代码库说明。
【HTML/XML】     <script> global=globalThis </script>

5. 运行时提示 process 不存在,vite 中已经不通过 process 获取自定义的变量,需要使用 import.meta,但是考虑到 vite 仅用于开发阶段,不应对项目进行破坏性兼容,因此考虑在全局自定义 process。vite 通过 define 配置自定义全局变量。

【Javascript】define: {// 单独使用这种方式 并不能在运行时获取 env 中设置的变量,    'process.env': process.env,}

      通过实现简单的命令行工具来根据当前运行环境读取配置文件来对 process 进行数据的补充:

【Javascript】// env 类型文件读取const dotenv = require('dotenv')// 扩展 process   const { expand } = require('dotenv-expand')// 命令行参数拆分   const minimist = require('minimist');// 获取环境变量function loadEnv (mode) {   const basePath = resolve(__dirname, `.env${mode ? `.${mode}` : ``}`)   const localPath = `${basePath}.local`
const load = envPath => {// 根据 当前 命令行 mode 读取 env 中的参数配置 const env = dotenv.config({ path: envPath, debug: process.env.DEBUG })// 扩展 process     expand(env) }
load(localPath) load(basePath)}// 获取命令行中的 参数 const parmas = minimist(process.argv.slice(2))// 目前只考虑 mode loadEnv(parmas.mode)
// ... export default defineConfig({ define: { 'process.env': process.env,})

6. rollup 中不支持动态require 打包编译,而由于H5 中多平台sdk 冲突问题,目前必须通过动态导入的方式避免 api 冲突,因此会导致浏览器报错。解决方案在模板文件中对sdk API 做兼容处理,防止报错。

      另外有其它兼容思路,如通过 import 替换 require,但是 import 为异步导入,需要配合顶层await 方式才能比较优雅的实现sdk 的动态导入,但是vue-cli 中目前没有通过配置实现顶层await 的兼容。

【HTML/XML】     <script>       function require({        return {          default: {            init: () => {},            scanCode: () => new Promise((resolve, reject) => {}),            initVoice: () => {},            startRecord: () => {},            stopRecord: () => {},            jumpPage: url => {              window.location.href = url;          },          ready: () => Promise.resolve(),          backPage: (delta = 1) => {             window.history.go(-delta);          },          postMessage: () => {},          getLocation: () => Promise.resolve({ lat: 0, lng: 0 })        }      }    }   </script>

7. @jd/pandora-mobile (京东物流内部组件库)组件兼容问题,组件库默认导出方式与 vite 打包不兼容(具体原因可以参考vite issue),解决方案是通过路径别名将 @jd/pandora-mobile路径指定为 commonjs 包,或者可以通过vite中 resolve.mainFields 配置调整包搜索的优先级顺序来解决。

【Javascript】  resolve: {    alias: {       '@jd/pandora-mobile': resolve(__dirname, 'node_modules/@jd/pandora-mobile/dist/pandora-mobile.js'),    },   // mainFields: ['main', 'module', 'jsnext:main', 'jsnext']}

a. @jd/pandora-mobile 组件库样式文件导入不生效,解决方案有两种,一种是通过配置 css 预处理插件配置(preprocessorOptions)将组件库样式添加为额外的全局样式,但是这种方案可能存在样式优先级的问题;第二种是方案是通过 vite 插件 vite-plugin-style-import,实现样式的按需导入。具体配置如下:

【Javascript】     import { createStyleImportPlugin } from 'vite-plugin-style-import'        createStyleImportPlugin({          libs: [{            libraryName: "@jd/pandora-mobile",            esModule: true,            resolveStyle: (name) => {               return `@jd/pandora-mobile/es/components/${name}/style/index.css`      },    }]   }),  //css: {  //  preprocessorOptions: {  //    scss: {  //      additionalData: `@import '${resolve(__dirname, 'node_modules/@jd/pandora-mobile/dist/pandora-mobile.css')}';`  //    }  //  }  //},

8. sass-loader 中 node-sass 与 sass 兼容问题(与内部组件库pandora相关),vite中依赖sass(dart-sass),而原项目中依赖node-sass。当两个依赖包同时存在时,由于 @vue/cli-service(v3.8.4)中设置了 sass-loader 优先依赖 sass(dart-sass),从而导致node-sass依赖被屏蔽,在通过原有的webpack方式进行打包时会由于 pandora 组件库与 sass(dart-sass) 不兼容导致打包失败。解决方案是通过调整 vue.config 配置,将sass-loader 中对 node-sass 的依赖优先级提高,以防止安装 sass 后通过 vue-cli 打包报错。

【Javascript】// vue-cli 3.8.4defaultSassLoaderOptions.implementation = require('sass')
// 调整 vue.configcss: { // ... loaderOptions: { // ...      // sass-loader 优先 使用 sass , pandora sass 兼容有问题      implementation: require("node-sass"),    }}


05 
  代码方面调整  


理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。

    常量的导入导出在文件之间存在循环依赖报错,需将常量统一导出处理。



06 
  总结  


理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将目标页面展示到屏幕。
两种项目启动结果对比如下图:

图片

图1 vite 启动H5工程

图片

图2 vue-cli 启动H5工程

1. 就结果来说 vite 在项目启动上确实速度很快,但是由于运行时打包的方式,首次页面交互体验卡顿明显;

2. sdk 兼容仍有待优化。

【Javascript】import { defineConfig, ViteDevServer, PluginOption, createServer } from 'vite'import legacy from '@vitejs/plugin-legacy'import { getBabelOutputPlugin } from '@rollup/plugin-babel'import html from 'vite-plugin-html'import { createVuePlugin } from 'vite-plugin-vue2'import { createStyleImportPlugin } from 'vite-plugin-style-import'import { envSwitchPlugin } from 'vite-plugin-env-switch';import { globalPolyfill } from 'vite-plugin-global-polyfill'import { green } from 'picocolors'
const dotenv = require('dotenv')const minimist = require('minimist');const { resolve } = require('path')// 获取环境变量function loadEnv (mode) { const basePath = resolve(__dirname, `.env${mode ? `.${mode}` : ``}`) const localPath = `${basePath}.local`
const load = envPath => { const env = dotenv.config({ path: envPath, debug: process.env.DEBUG }) process.env = Object.assign({...process.env}, env.parsed) }
load(localPath) load(basePath)}const parmas = minimist(process.argv.slice(2))
loadEnv(parmas.mode)
export interface PluginConfig { envKey?: string, strGetter?: () => string}
// https://vitejs.dev/config/export default defineConfig({ base: '/express-vite/', publicDir: 'public', resolve: { alias: { '@': resolve(__dirname, 'src'), '~@': resolve(__dirname, 'src'), // https://github.com/vitejs/vite/issues/1724#issuecomment-767619642 // vite 读取的文件 跟 esbuild 读取的文件不一致, vite 读取的 是 commonjs,但是 esbuild 找到了 esm 类型的文件认为 不需要转换,所以导致导出没有做兼容 // '@jd/pandora-mobile': resolve(__dirname, 'node_modules/@jd/pandora-mobile/dist/pandora-mobile.js'), }, // 用于更改 包搜索的优先级 具体原理见 源码 resolvePackageEntry 方法 // 等待新版本有 config.optimizeDeps?.needsInterop 配置,替换为该方案 mainFields: ['main', 'module', 'jsnext:main', 'jsnext'] }, server: { host: 'xxx.jd.com', https: true, port: 443, open: true, }, optimizeDeps: { }, plugins: [// 本地开发 通过页面按钮 动态切换 项目环境// https://github.com/PengBoUESTC/vite-plugin-env-switch envSwitchPlugin({ wsProtocol: 'vite-hmr', envs: ['prepare', 'development', 'production'], wsPath: 'wss://xxx.jd.com/express-vite/', root: __dirname, eventName: 'env-check' }), globalPolyfill(), createStyleImportPlugin({ libs: [{ libraryName: "@jd/pandora-mobile", esModule: true, resolveStyle: (name) => { return `@jd/pandora-mobile/es/components/${name}/style/index.css` }, }] }), createVuePlugin({}), legacy({ targets: ['defaults', 'not IE 11'], }), getBabelOutputPlugin({ configFile: resolve(__dirname, 'babel.config.js'), }), html({ inject: { injectData: { title: '京东快递', }, }, minify: true, }), ], css: { preprocessorOptions: { // scss: { // additionalData: `@import '${resolve(__dirname, 'node_modules/@jd/pandora-mobile/dist/pandora-mobile.css')}';` // } } },
build: { outDir: 'dist', target: 'es2015', minify: 'terser', rollupOptions: { plugins: [ ], }, }, define: {    'process.env': process.env, }})
图片

图片

推荐阅读
混沌演练实践(一)
京东小程序CI工具实践
会员徽章系统 - 整体介绍及方案设计
Redis为什么这么快?

图片

求分享

图片

求点赞

图片

求在看

打造SAAS化服务的会员徽章体系,可以作为标准的产品化方案统一对外输出。结合现有平台的通用能力,实现会员行为全路径覆盖,并能结合企业自身业务特点,规划相应的会员精准营销活动,提升会员忠诚度和业务的持续增长。
底层能力:维护用户基础数据、行为数据建模、用户画像分析、精准营销策略的制定

功能支撑:会员成长体系、等级计算策略、权益体系、营销底层能力支持

▪用户活跃:会员关怀、用户触达、活跃活动、业务线交叉获客、拉新促活

继续滑动看下一个
京东技术
向上滑动看下一个