亚太娱乐平台报道腾讯愉快鼠英语 小程序实践与总结_亚太娱乐平台官网资讯

来自:腾讯在线教训技术 2020-06-04

腾讯愉快鼠英语 团队中有很多小程序的项目,且后续还会很多小程序的开拓和迭代计划,因此我们团队是小程序的重度使用者。在小程序的开拓中,团队积存了一些技术和经验,也遇到了一些困苦和挑战,还踩了很多坑,因此有必要将我们团队的小程序实践进行总结和分享。

腾讯愉快鼠英语

一、工程化探究

微信小程序的开拓规范里,有一些工程方面的要求,例如能够通过项目的配置文件来设置根目录,每个页面或组件需要wxml、js、json、wxss 4个文件组成等,于此同时微信的开拓者工具能够帮助初始化项目,并设置好目录结构。除此之外应付项目工程方面的撑腰就比较脆弱了。为了提升团队的开拓效率和质量,我们还需要在已有的基础之上进行一些列的优化。我们希望小程序的开拓脚手架至少具备以下的能力:

    css预处置语言撑腰,能够使用例如sass、postcss等开拓样式;

    typescript撑腰,能够更好地使用typescript进行开拓;

    更好的目录结构设计;

    更好的npm包撑腰;

    代码检验撑腰,能够使用eslint/tslint、stylelint等对代码进行规制校验;

因为现阶段的小程序开拓的工程需求主要集中在文件编译和资源整理上,小程序开拓者工具会帮我们处置文件打包,因此我们考虑使用gulp去搭建工程脚手架。

1. 目录结构设计

微信小程序的代码主要由4个放置在同一目录下的文件构成:

    .json 后缀的? JSON 配置文件; .wxml 后缀的? WXML 模板文件; .wxss 后缀的? WXSS 样式文件; .js 后缀的? JS 脚本逻辑文件;

微信开拓者工具会对以上的文件进行监听,当其中任意一个文件发生改变时,开拓者工具就会刷新预览。

如果使用ts进行开拓,那在同一目录下,还将多出ts文件;如果参考这种方式引入css的预编译语言,那还会再多出一个待编译的样式文件。这样一个页面或组件的目录下,就至少会存在6个文件,显得非常臃肿,不但降低了文件查找的效率,还有可能带来别的的误操作。因此这样的目录结构就不敷以支撑我们后续开拓的工程化要求。

我们希望将源文件和编译文件离别,只保留基本的4种类型文件,当源文件发生改变时,就触发编译,将其编译成对应的开拓者工具能够监听的文件类型。因此设计了以下新的目录结构:

目录结构

src 目录下存放项目的源文件,使用gulp监听文件的变化,并触发对应的编译任务,将源文件编译为目的文件,或者拷贝不需要编译的文件到目的目录(dist文件夹),然后在project.config.json里指定小程序的根目录为dist文件夹,这样开拓者工具就会去监听dist目录里的文件变化并更新预览了。通过这样的设计,整个目录结构更加清楚,开拓者只需要关切src目录即可。

当然在实践的进程中,这样的目录结构也存在肯定的问题:

    从文件变化到最后开拓者工具更新预览的整个链路变长了,有肯定的时间上的损耗;

    因为整个链路变长,也加大了引入别的问题的可能;

    因为一些历史原因,团队一些老的项目还没有完全按照这样的目录结构去设计,团队中的小程序项目目录结构还没有完全统一;

以上的问题还需要我们团队在后续的开拓中去解决和优化。

2. css预处置语言撑腰

微信小程序的样式代码主要是编写在wxss文件中,其语法和css是一样的,只有少量的css法则不适用。如果只是编写css样式,那只写wxss是完全没问题的。但是现在市面上还是有很多流行的css预处置语言能够帮助更好地开拓css样式,提供了例如mixins、function、变量等功能。因此我们希望编写css预处置语言,并将其编译成wxss文件。

我们团队使用的是postcss。源文件编写的是css文件,通过设置gulp task的方式,将css编译成wxss。并通过postcss插件的方式,集成更多的别的功能。

在实践进程中,我们发现当项目范围开头变大时,在有些机器上,修改一次样式文件后触发更新的速度很慢,这个时刻我们就引入cache去加速css的编译。第一次编译时会将一切的css编译,尔后只会去编译修改过的文件。代码如下:

/**

* 将 css 编译成 wxss

*/

const cssCompile = () =>

src([`${mpDir}/**/*.css`, `!${mpDir}/**/_*.css`])

.pipe(cache('css-compile'))

.pipe(

// 防止编译中断

postcss().on('error', () => {

this.emit('end');

})

)

// 去掉编译出来的 :root{}

.pipe(replace(/:root\s\{[^}]*\}?\s*/, ''))

.pipe(

rename((path) => {

path.extname = '.wxss';

})

)

.pipe(dest((file) => file.base));

3. typescript撑腰

原先的微信小程序对ts文件的撑腰,是通过预置编译脚本,使用 tsc 去编译ts文件的。我们的项目引入了gulp之后,对ts的撑腰就是通过设置gulp task,使用gulp-typescript 这个插件去编译ts文件,同时还需要使用gulp-sourcemaps这个插件去写入sourcemap。

在实践进程中,我们发现有的时刻ts的文件编译比较慢,这个时刻能够使用gulp-typescript提供的增量编译的功能。开启增量编译之后,第一次编译时的速度是一样的,而之后的编译速度就会提升约50%。代码如下:

const tsProject = ts.createProject("tsconfig.json");


// 编译 ts

const tsCompile = () =>

gulp

.src(tsPath)

.pipe(sourcemaps.init())

.pipe(

/* 增量编译 */

tsProject()

)

.js.pipe(sourcemaps.write())

.pipe(gulp.dest(dist));

4. 更好的npm包撑腰

微信小程序是撑腰使用npm包的,但是这个撑腰是有一些前提条件的。例如当引入某个包时:

import { abcRequest } from '@tencent/abcmouse-sdk-mp-tools';

小程序会去根目录下的miniprogram_npm这个文件夹下查找有没有@tencent/abcemouse-sdk-mp-tools这个包,如果没有则会提示找不到对应的包。而miniprogram_npm又是依据package.json里的dependencies字段里声明的依赖构建而成的。因此微信小程序要使用npm包的前提总结如下:

    必须在package.json的dependencies字段里有声明 ;

    小程序的根目录下必须有node_modules目录,其目录里有对应的包;

    必须构建出miniprogram_npm;

因此要想使用npm包,整个进程是比较曲折的。好在小程序官方有提供对应的ci构建能够帮助我们。

但在实践进程中发现,挪用ci.packNpmManually这个接口构建出来的miniprogram_npm目录,不但包含了dependencies里的依赖,还包含了别的的依赖,而miniprogram_npm这个目录里的代码在上传小程序代码时也是会上传的,引入别的多余的依赖会增大小程序的包体积,在小程序严厉的代码大小要求下,这是不可取的。因此还需要对构建的包进行筛选。其大致流程如下:

流程

使用gulp监听package.json文件,当安装新的npm包,并指定 --save 时,package.json文件会发生变化,并触发对应的gulp task。在gulp task里去遍历package.json的denpendcies字段,并从顶层目录的node_modules里拷贝对应的npm包放入dist目录的node_modules中。最后再通过ci.packNpmManually方法去构建,这个时刻构建出来的miniprogram_npm目录里就只有必须的npm包了。

通过这种方式,我们需要使用新的npm包时,就只需要npm install并在代码中import就能够了,别的的处置进程对开拓者来说都是无感知的。相应的gulp task代码如下:

/**

* 在小程序根目录下生成package.json文件用于构建miniprogram_npm

* @param {Array<string>} deps denpendencies工具

*/

const generateSubPkg = (deps) =>

writeJsonFile(subPkgPath, { dependencies: deps })

.then(() => deps);


/**

* 猎取必要的npm包目录路径

* @param {Array<string>} deps 依赖数组

*/

const getDepsModule = (deps) =>

Object.keys(deps).map((key) => `node_modules/${key}`);


/**

* 构建miniprogram_npm

* @param {Array<string>} modules npm包路径数组

*/

const packNpmManually = (modules) => {

const packPath = `${mpDir}/miniprogram_npm`;

const subNpmPath = `${mpDir}/node_modules`;


fsx.emptyDirSync(packPath);

fsx.emptyDirSync(subNpmPath);


modules.forEach((modulePath) => {

fsx.copySync(modulePath, `${mpDir}/${modulePath}`);

});


return ci.packNpmManually({

packageJsonPath: path.resolve(process.cwd(), subPkgPath),

miniprogramNpmDistDir: path.resolve(process.cwd(), mpDir),

});

};


/**

* 构建miniprogram_npm gulp plugin

*/

const packPkgManually = () =>

through.obj(function (chunk, enc, cb) {

const filepath = path.resolve(process.cwd(), 'package.json');


if (!fsx.pathExists(filepath)) {

cb(null, chunk);

}


const pkgData = fsx.readJSONSync(filepath);


const dependencies = pkgData.dependencies || {};


// denpendencies 没有发生变动则不需要构建

if (isEqual(cached, dependencies) || packing) {

cb(null, chunk);

}


const spinner = ora('开头构建npm包...').start();


packing = true;


generateSubPkg(dependencies)

.then(getDepsModule)

.then(packNpmManually)

.then((result) => {

cached = dependencies;

spinner.succeed('构建胜利,构建结果:');

packing = false;

console.log(result);

cb(null, chunk);

})

.catch((err) => {

spinner.fail('构建失败');

packing = false;

console.error(err);

});

});


const pkgPack = () => src(pkgPath).pipe(packPkgManually());


const pkgWatch = () => {

watch(pkgPath, pkgPack);

};

5. 代码校验

我们团队还接入了imweb团队的eslint和stylelint法则去做代码校验,这里就不具体暂开讲了,感兴趣的能够参考:

    eslint-config [ 1 ];

    stylelint-config [ 2 ? ]

6. 后续展望

本来在小程序的工程化这一块还有很多工作能够做,例如:

    当项目体积变大时,小程序开拓者工具的刷新预览速度变慢,除了守候小程序官方的优化之外,是否能够通过工程化的手段,提升开拓者工具的预览刷新速度;

    合理的分包能够提升小程序的加载速度,是否能够通过工程化的手段,将项目中用到的通用组件,智能合理地分配到主包和子包中;

    引入命令行工具生成页面和组件的文件模板;

    尝试引入tree-shaking剔除无用的代码;

    尝试引入purifycss剔除无用的样式;

    集成官方的ci工具,完善开拓体验;

...

我们团队会继续在小程序工程化方面进行探究和尝试,如果有新的结果我们也会及时分享。

二、性能优化

因为微信小程序是运行在微信app里的,所以其运行情况是比较苛刻的,因此要想使小程序流畅地运行,提供良好的用户体验,对其进行性能优化就至关重要。

应付小程序优化来说,一些传统的前端优化方案也适用于小程序。而其双线程的设计模式,又和传统前端的单线程有所差别,因此也有一些新的优化点,下面主要从几个大的方面概括总结:

    加快网络要求 ;

    加快页面渲染;

    提升渲染性能 ;

    内存优化 ;

    别的优化。

1. 加快网络要求

1.1 减小代码包大小

小程序在冷启动时,会首先下载对应的代码包,然后解压执行代码,所以减小包大小能够加快代码下载和解压的速度。一些方法能够减小代码包的大小:

代码复用,尽量将能够复用的代码提取封装做出npm包或通用函数;

剔除无用的样式,能够使用类似 purifycss的库将无用的css样式剔除以减小样式代码的大小;

剔除无用的函数,能够尝试引入tree-shaking剔除无用的代码;

静态资源走cdn,尽量不要将静态资源打包到代码中;

分包,能够将一些和亚太娱乐平台渲染无关的代码分发到子包中从而加快主包的下载和执行。

1.2 预要求

数据预拉取:小程序提供了一个接口,能够让用户在进入到小程序之前就去要求接口,当然这个方法有肯定的限制,需要深入调研和小心使用;

分包预下载:能够配置进入某个页面时下载可能会用到的分包,能够有效幸免进入某些页面”白屏“时间过长。

1.3 缓存

没有什么要求比不要求更快的了,合理使用缓存能够有效减少网络要求的数量,加快整体的加载速度。应付一些实时性不高的数据,我们能够使用微信提供的缓存能力,将一些数据存储在本地,从而幸免一些网络要求。

1.4 金鼎娱乐 百家乐优化

webp格式金鼎娱乐 百家乐;

金鼎娱乐 百家乐剪裁和降质;

金鼎娱乐 百家乐懒加载和雪碧图;

渐进式加载大图资源:在不得不使用大图资源的场景下,能够适当使用“体验换速度”的措施来提升渲染性能。小程序会把已加载的静态资源缓存在本地,因此,应付大图资源,我们能够先呈现高度压缩的模糊金鼎娱乐 百家乐,同时使用一个隐藏的 <image> 节点来加载原图,待原图加载完成后再转移到真实节点上渲染。整个流程,从视觉上会感知到金鼎娱乐 百家乐从模糊到高清的进程,但与对首屏渲染的提升效果相比,这点体验落差是能够接收的。

2. 加快页面渲染

本来小程序的页面渲染的优化思路和传统前端的优化思路是一致的,主要思想是: 关键渲染路径渲染

关键渲染路径(Critical Rendering Path)是指完成屏幕渲染的进程中必须发生的事情。

我们能够分析页面上哪些局部是主要模块,哪些局部是次要模块,例如一些提示性的组件,我们能够稍后渲染。

于此同时,还能够在主要模块中还能够优先渲染主屏模块,不在主屏内的模块能够延迟加载或者滚动加载。

3. 提升渲染性能

提升渲染性能能够有效减少交互时的卡顿,让用户在交互的时刻体验”如丝般流畅“。小程序采纳的双线程模型,即渲染和逻辑分散在两个差别的线程中。所以在小程序的情况下,加快用户响应主要从以下3点动身:

    降低线程之间的通信频次;

    减少线程之间通信的数据量;

    减少wxml的节点数量。

能够采纳的措施有:

    合并setData挪用;

    只把与界面渲染相关的数据放在data中;

    去掉不消要的事情绑定;

    去掉不消要的节点数据;

    事情总线治理数据;

    滚动渲染长列表节点;

...

4. 内存优化

因为小程序毕竟是一个程序(微信)中的程序,可提供应其运行的情况资源是十分有限的,这应付小程序的设计和开拓来说就比较苛刻了,因此要在实现的时刻小心翼翼。能够从以下几个方面注意内存的优化:

    内存预警;

    回收后台页面计时器;

    幸免触发事情中的重度内存操作;

    大图、长列表优化。

5. 别的优化

还能够采纳一些别的的方式来对小程序进行优化:

    逻辑后移:能够让后台承担更多的业务实现。小程序端主要承担展示相关的职责,这样能够幸免在小程序端进行过多的数据操作,占用过多的内存。也能够幸免因为复制的业务实现而带来的潜在的程序崩溃;

    骨架屏:能够使用骨架屏来提升整体的加载体验。

最后总结输出一下小程序性能优化相关的脑图如下:

脑图

三、主动化探究

1. 主动化测验

小程序官方提供了主动化地工具去仿照小程序的操作,搭配常用的测验框架,能够很容易地实现小程序的前端测验。小程序的仿照器提供了4个级别的操作api:

    仿照器级别的 AutoMator;

    小程序级别的 MiniProgram;

    页面级别的 Page;

    元素级别的 Element。

我们能够使用这几个api工具去仿照小程序的行为,例如仿照元素点击、页面数据修改、页面跳转等操作。具体api能够参考:小程序主动化工具 [ 3 ]。

在实践接入的进程中遇到了一些坑。要想使用小程序仿照器是有一些前提条件的,首先是要开启开拓者工具安定设置中的 CLI/HTTP 挪用功能。并通过以下代码开启挪用:

automator.launch({

cliPath: 'path/to/cli', // 工具 cli 身分

projectPath: 'path/to/project', // 项目文件地点

})

其中 cliPath 是我们的开拓者工具的文件路径,如果没有更改过默认安装的身分则能够疏忽。projectPath是项目目录的地点,这里的项目目录指的是project.config.json里指定的目录,并且这个路径必须是绝对路径才能够挪用胜利。

其次小程序的仿照器是有一些使用限制的,它不克挪用和操作微信系统的原生组件的,例如授权、身分、支付等功能。当我们希望使用脚本去测验整个页面的流程时,当涉及到授权和支付等操作时,往往就跑不下去,因此小程序的仿照器更适合去做一些关键操作的测验,例如测验某些操作之后,页面的样式、行为是否相符预期。

2. CI接入

小程序的代码打包、上传等功能能够交由CI来操作。我们团队主要使用蓝盾CI,配合蓝盾上的小程序插件,能够很轻松地接入,其流程比较简单,主要是拉取代码、安装依赖和构建上传。蓝盾流水线如下:

蓝盾流水线

小程序代码上传需要用到秘钥,我们能够将其秘钥托管在蓝盾,即方便又安定。

References

[1] eslint-config:? //github.com/imweb/eslint-config-imweb

[2] stylelint-config:? //github.com/imweb/stylelint-config-imweb

[3] 小程序主动化工具:? //developers.weixin.qq.com/miniprogram/dev/devtools/auto/

var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?fc03c8be482cb50421070dc544ea100c"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); (function(){ var src = document.location.protocol +'//js.passport.qihucdn.com/11.0.1.js?0cafbe109ab248eb7be06d7f99c4009f'; document.write('