Me and this article

I am a front-end enthusiast and am now a junior. I started to develop small programs in my sophomore year. At present, the only project I am still working on is uCourse, a course evaluation program for students in my school.

I have used wechat applet native language to develop small programs, as well as a series of third-party frameworks that have followed them, such as WePY (1.x), MPvue (1.x), and Taro (0.x to date).

The reason for the emergence of so many third-party frameworks, in my opinion, is the irrationality and defects of wechat applet native language, and the emergence of a batch of “XX applet” in the late stage has strengthened the importance of multi-side compilation requirements. I was lucky enough to experience the development process of these frameworks from the early stage to the present. I also had some comparison feelings of my own, and finally chose Taro.

In this article, I would like to briefly summarize some of my experience and difficulties in using Taro to develop small programs, as well as all aspects of my psychological process when ENTANGLED with wechat small programs… (Mainly wechat small program, I have not developed more terminal.)

In addition to Taro itself, there will also be some small program development practices. Anyway, I hope I can help you.

Native applets vs. third party frameworks

The pain point of native applets development

What are the pain points of native applets development? Taro: Why do we use Taro to write small programs?

To sum up:

  • Cumbersome single-page file structure (as many as four)
  • Syntax like React, Vue, or wind
  • Structure/method naming conventions are not unified (line splitting/word linking/mixed hump writing)
  • Incomplete front-end development experience (lack of Webpack, ES 6+ syntax, CSS preprocessor, etc.)

Of course, the official team of wechat mini program is also constantly improving and developing. However, the speed of iteration and the willingness of the developer community to respond to questions was really impressive…

Overview of Third-party Frameworks

Present (March 2019) —

  • WePY (class vue.js)
  • Mpvue (vue.js + H5/ Baidu/byte/Alipay)
  • Min (class vue.js)
  • Taro (React. Js + H5/ Baidu/byte/alipay /RN)
  • Nanachi (react. js + H5/ Baidu/byte/Alipay)
  • Uni-app (Vue.js + H5/ Baidu/Byte/Alipay /Native)

As to which one is better, I can’t quite compare the frameworks in a comprehensive way right now because they’re all iterating. I suggest you look at the community dynamics, look at the need for multi-compiled, and look at how the technical stack fits your team’s skills.

In July and August last year, before the last two frameworks were released, I compared the first four: Because WePY has too many holes, Min community is not active, mpvue has almost no activity after open source, and I personally like React. Taro’s community is active and version iteration speed is good, so it is no surprise that I choose Taro. But now that the first two frameworks are under 2.x development, two more frameworks are popping up… So you can play around a little bit. _ _ (: з < “)

Taro start

So without further ado, let’s return to Taro…

Taro’s development experience is similar to React’s. If you have experience developing React, you should have no trouble getting started. If not, there is no problem getting started by reading Taro’s official documentation.

From installation to creating a new project to use —

$ npm install -g @tarojs/cli
$ taro init myApp
$ cd myApp
$ npm install
# development
$ npm run dev:weapp
# compiler
$ npm run build:weapp
Copy the code

When h5 / Swan/Alipay/TT/RN is substituted in the development and compilation instructions herein, it can be compiled and run on other corresponding terminals. Multi-side code logic can be different, see cross-platform development for details.

The project structure

There is an official article on redux-based project best practices: Taro Deep Development Practices.

The official recommended project structure

├ ─ ─ the config configuration directory | ├ ─ ─ dev. The development of a js configuration | ├ ─ ─ index. The default configuration | js └ ─ ─ the prod. Js packaging configuration ├ ─ ─ the SRC source directory | ├ ─ ─ components common components directory | ├ ─ ─ Pages page file directory | | ├ ─ ─ index index page directory | | | ├ ─ ─ banner page index private components | | | ├ ─ ─ index. The js index page logic | | | └ ─ ─ index. The CSS Index page styling | ├ ─ ─ utils public method library | ├ ─ ─ app. The CSS style of total project general | └ ─ ─ app. Js project entry documents └ ─ ─ package. The jsonCopy the code

I didn’t use Redux/MobX on the project, and the structure of the project was pretty straightforward once it “grew”

├ ─ ─ dist directory compiled results ├ ─ ─ the config configuration directory | ├ ─ ─ dev. The development of a js configuration | ├ ─ ─ index. The default configuration | js └ ─ ─ the prod. Js packaging configuration ├ ─ ─ the SRC source directory | ├ ─ ─ assets Public resources directory (such as embedded icon resources) | ├ ─ ─ components common components directory | | └ ─ ─ Btn common components Btn directory | | ├ ─ ─ Btn. Js common components Btn logic | | └ ─ ─ Btn. SCSS common components Btn style | ├ ─ ─ pages page file directory | | └ ─ ─ index index page directory | | ├ ─ ─ the components the index page of unique component directory | | | └ ─ ─ Banner Banner component directory | index page | | ├ ─ ─ Banner. Js index page Banner component logic | | | └ ─ ─ Banner. The SCSS index page Banner component style | | ├ ─ ─ index. The js index page logic | | └ ─ ─ Index. The SCSS index page styling | ├ ─ ─ subpackages subcontract directory (proposed the subcontract project is too large) | | └ ─ ─ profile, a profile of the subcontract directory | | └ ─ ─ pages the subcontract page file directory | | └ ─ ─ the index page of the subcontracting index directory (its structure and the main package page documents consistent) | ├ ─ ─ auxiliary tools directory utils project | | └ ─ ─ API, js, such as auxiliary class API | ├ ─ ─ app. The CSS style of total project general | └ ─ ─ ├ ── package.json app.js project entry fileCopy the code

What…… Is this also called “simple and clear”? (゚ ゚≡゚д゚)

This is the way I personally prefer to organize. My project is not small, a total of nearly 30 pages, using the above way to maintain, really feel quite easy and comfortable. Of course, you can organize the files as you like

Compiling configuration files

The compile configuration is stored in the config directory under the project root and contains three files

  • index.jsIs a common configuration
  • dev.jsIs the configuration for the project preview
  • prod.jsIs the configuration when the project is packaged

Here are some use cases and some pit solutions

Path alias

The consequence of constantly importing relative paths in a project is that the directory structure is not easily and intuitively understood; If you want to move and change a directory, and the IDE does not have very accurate refactoring, you need to manually change the path of each import, which is very uncomfortable.

So we wanted to put:

import A from '.. /.. /componnets/A'
Copy the code

become

import A from '@/componnets/A'
Copy the code

This kind of reference.

The method is as follows:

/* config/index.js */
const path = require('path')
alias: {
  '@/components': path.resolve(__dirname, '.. '.'src/components'),
  '@/utils': path.resolve(__dirname, '.. '.'src/utils'),},Copy the code

See: nervjs. Making. IO/taro/docs/c…

Determine the environment in the code

/* config/dev.js */
env: {
  NODE_ENV: '"development"'.// JSON.stringify('development')
},
Copy the code
/* config/prod.js */
env: {
  NODE_ENV: '"production"'.// JSON.stringify('development')
},
Copy the code

NODE_ENV === ‘development’ in the code to determine the environment.

Apis to distinguish between development and online environments

/* config/dev.js */
defineConstants: {
  BASE_URL: '"https://dev.com/api"',},Copy the code
/* config/prod.js */
defineConstants: {
  BASE_URL: '"https://prod.com/api"',},Copy the code

This way, you can reference BASE_URL directly in your code to get a different API Gateway based on your environment.

Solve the problem of style loss after packaging

If you have no problem with the styling in the development environment, but some of the styling is missing after compilation and packaging, it may be due to csSO’s 0 feature. You can turn this off in plugins.csso:

/* config/prod.js */
plugins: {
  csso: {
    enable: true.config: {
      restructure: false,}}},Copy the code

Resolve error in compiling compressed JS files

If you encounter a compiler error when compiling a compressed JS file, you can exclude it from compilation:

/* config/index.js */
weapp: {
  compile: {
    exclude: [
      'src/utils/qqmap-wx-jssdk.js'.'src/components/third-party/wemark/remarkable.js',]}},Copy the code

Fixed an issue where resource files could not be found after compilation

If you have a resource file (such as an image) that was not compiled into the dist directory after compilation and cannot be found, you can copy it directly:

/* config/index.js */
copy: {
  patterns: [{from: 'src/assets/'.to: 'dist/assets/',}]},Copy the code

Use wechat applet native third-party components and plug-ins

The official document: nervjs. Making. IO/taro/docs/m… .

Note that if you do this, the project cannot compile more than once.

See the official document description, very simple. But I actually stepped on the pit in the process of using it. For example, I tried to integrate wemark to do markdown rendering. Two problems were found:

  1. Taro will miss compilation only inwxssReferenced inwxssFile. The solution is needcopyConfigure to copy all files at compile time.
  2. When the compressed JS file is compiled, it will be compiled again, resulting in an error, and ignoredcopyConfiguration. The solution is needexcludeThe configuration excludes compressed JS files. (As mentioned above.)

So take wemark as an example. The project integrates native components and requires additional configuration:

/* config/index.js */
copy: {
  patterns: [{from: 'src/components/wemark'.to: 'dist/components/wemark',}]},weapp: {
  compile: {
    exclude: [
      'src/components/wemark/remarkable.js',]}},Copy the code

And then you can quote —

import Taro, { Component } from '@tarojs/taro'
import { View } from '@tarojs/components'

export default class Comp extends Component {
  config = {
    usingComponents: {
      wemark: '.. /components/wemark/wemark'
    }
  }

  state = {
    md: '# heading'
  }

  render() {
    return (
      <View>
        <wemark md={this.state.md} link highlight type="wemark" />
      </View>)}}Copy the code

In short, if you’re having similar problems integrating native components, try copying the entire component directory and excluding some JS files to prevent overcompilation.

Use the icon font component

We want to have our own icon font component in the project, using it as follows:

<UIcon icon="home" />
Copy the code

Why UIcon, not Icon? Because the name does not conflict with the official component Icon. (| | | ゚ д ゚) you can also call him OhMyIcon and so on.

Here first say practice, say pit again…

In practice, if you don’t have a professional designer or in-house icon library, you can use Iconfont’s icon library, which has the advantages of multiple ICONS and CDN out of the box. You can create a new project, select the appropriate icon for your project, and get the @font-face reference code directly:

Unicode references are almost identical to Font class references, which have the advantage of semanticizing the class name, and Unicode is recommended because we need another layer of wrapping to make the class name customizable.

The advantage of Symbol is that it supports multi-color ICONS, so why not use it? Wechat applet is not compatible with SVG icon QwQ. I searched a lot of posts in the official community and they would just say “ok, let’s see”, “please post code snippets” and then nothing happened… There are a lot of similar situations, mention the bug for several years, always do not repair, keep when the family heirloom… (/ д ‘) / ~ ╧╧)

Add @font-face to the component’s style file and write something like this:

/* UIcon.scss */
.u-icon {
  display: inline-block;
  &:before {
    font-family: 'iconfont' ! important;
    font-style: normal;
    font-weight: normal;
    speak: none;
    display: inline-block;
    text-decoration: inherit;
    width: 1em;
    text-align: center; }}Copy the code

Then, for each icon, give its Unicode definition:

/* UIcon.scss */
.u-icon-add::before {
  content: '\e6e0';
}
.u-icon-addition::before {
  content: '\e6e1';
}
/ *... * /
Copy the code

Uicon.js is wrapped like this:

/* UIcon.js */
import Taro, { Component } from '@tarojs/taro'
import { View } from '@tarojs/components'
import './UIcon.scss'

export default class UIcon extends Component {
  static externalClasses = ['uclass']

  static defaultProps = {
    icon: ' ',
  }

  render() {
    return <View className={`uclass u-icon u-icon-The ${this.props.icon} `} / >}}Copy the code

Notice here that I added an externalClasses configuration and a uclass className. The reason is that I want to define the internal style outside of the component, so that after defining it, I can call:

<UIcon icon="home" uclass="external-class" />
Copy the code

See external and global styles for document components for details. (This is the document I helped to fill after stepping on the hole…)

Package a reportformIdThe components of the

If you have the need to actively send applets template message cards, you may need such a component.

The applet’s current policy is that you can only report a one-time 7 day expired formId after the user triggers a button click event, which you use to send a template message once. So a group of smart small program developers emerged, button wrapped in the entire page, the user every click to report a formId, save after seven days, anyway, slowly. And the authorities seem to have been turning a blind eye… (low ` 艸 ´)

Taro is also very simple to implement such a wrapper:

/* FormIdReporter.js */
import Taro, { Component } from '@tarojs/taro'
import { Button, Form } from '@tarojs/components'
import './FormIdReporter.scss'

export default class FormIdReporter extends Component {
  handleSubmit = e= > {
    console.log(e.detail.formId) // formId is handled here
  }

  render() {
    return (
      <Form reportSubmit onSubmit={this.handleSubmit}>
        <Button plain formType="submit" hoverClass="none">
          {this.props.children}
        </Button>
      </Form>)}}Copy the code

When called, wrap the entire page around it:

</ confusing > {/* Some other components */} </ confusing >Copy the code

Note that the Button here needs to be “hidden” by using the following style code to remove the default style:

/* FormIdReporter.scss */
button {
  width: 100%;
  border-radius: 0;
  padding: 0;
  margin: 0;
  &:after {
    border: 0; }}Copy the code

Use decorators for quick share/login validation

Since I learned this part from other sources as well, and have an established tutorial, I won’t embellish it. Reference:

  • Use a Decorator to quickly implement applet sharing
  • Use a Decorator to implement the application in Taro

The following said these, more is about the small program itself some practical cases. Of course, it is also set in Taro.

I18n internationalization

Because the program needs to realize the internationalization of mini program text, I searched for many cases, and finally referred to the idea of a relatively simple solution: appegrab-i18n. It’s been used in two projects. In Taro, this class can be packaged as follows:

/* utils/i18n.js */
export default class T {
  constructor(locales, locale) {
    this.locales = locales
    if (locale) {
      this.locale = locale
    }
  }

  setLocale(code) {
    this.locale = code
  }

  _(line) {
    const { locales, locale } = this
    if (locale && locales[locale] && locales[locale][line]) {
      line = locales[locale][line]
    }

    return line
  }
}
Copy the code

Create a new locales.js file and write your locales.js key with the same name as the wechat system language:

/* utils/locales.js */
locales.zh_CN = {
  Discover: 'found'.Schools: 'school'.Me: '我'.'Courses of My Faculty': 'My Departmental Courses'.'Popular Evaluations Monthly': 'Review of the Month'.'Popular Evaluations': 'Popular Reviews'.'Recent Evaluations': 'Latest Review'.'Top Courses': 'High Score Course'./ *... * /} locales.zh_TW = { ... locales.zh_CN,Discover: 'found'.Schools: 'school'.Me: '我'.'Courses of My Faculty': 'My Departmental Courses'.'Popular Evaluations Monthly': 'Review of the Month'.'Popular Evaluations': 'Popular Reviews'.'Recent Evaluations': 'Latest Review'.'Top Courses': 'High Score Course'./ *... * /
}
Copy the code

To use app.js, initialize first:

/* App.js */
componentWillMount() {
  this.initLocale()
}

initLocale = (a)= > {
  let locale = Taro.getStorageSync('locale')
  if(! locale) {// Initialize the language
    const systemInfo = await Taro.getSystemInfo()
    locale = systemInfo.language // The system language is used by default
    Taro.setStorage({ key: 'locale'.data: locale })
  }
  Taro.T = new T(locales, locale) // Initialize the localization tool instance and inject taro.t
  // Manually change the TabBar language (currently you can only do this)
  Taro.setTabBarItem({
    index: 0.text: Taro.T._('Discover'),
  })
  Taro.setTabBarItem({
    index: 1.text: Taro.T._('Me'),})}Copy the code

Components used in:

<Button>{Taro.T._('Hello')}</Button>
Copy the code

If the applet provides the function of changing the language, the user can save the configuration after the change, and then directly Taro. ReLaunch to the home page, and then change the language of TabBar as described above.

It’s a bit of a setback, but in my opinion, it’s the easiest and most feasible way to internationalize in a small program… (* ´ omega `) people (´ omega ` *)

Wrapper API method

Although Taro provides an Taro. Request method, I chose the fly.js library to wrap my own request method:

/* utils/request.js */
import Taro from '@tarojs/taro'
import Fly from 'flyio/dist/npm/wx'
import config from '.. /config'
import helper from './helper'

const request = new Fly()
request.config.baseURL = BASE_URL
const newRquest = new Fly() // This is used for locking

// Request interceptor, which I'm using here: except for some routes, if a user without permission "oversteps the bounds", an error message will be reported
request.interceptors.request.use(async conf => {
  const { url, method } = conf
  const allowedPostUrls = [
    '/login'.'/users'.'/email',]const isExcept = allowedPostUrls.some(v= > url.includes(v))
  if(method ! = ='GET' && !isExcept) {
    try {
      await helper.checkPermission() // a method to check user permissions
    } catch (e) {
      throw e
    }
  }

  return conf
})

If the user's session expires, the request queue is locked, re-logon is completed, and the request queue continues
request.interceptors.response.use(
  response= > response,
  async err => {
    try {
      if (err.status === 0) { // Network problems
        throw new Error(Taro.T._('Server not responding'))}const { status } = err.response
      if (status === 401 || status === 403) { // These two status codes indicate that the user has no rights and needs to log in to the primary session again
        request.lock() // Lock the request queue to avoid repeated requests
        const { errMsg, code } = await Taro.login() // Log in again
        if (code) {
          const res = await newRquest.post('/login', { code }) // Complete the login with the new instance
          const { data } = res.data
          const { sessionId, userInfo } = data
          Taro.setStorageSync('sessionId', sessionId) // Save the new session
          if (userInfo) {
            Taro.setStorageSync('userInfo', userInfo) // Update user information
          }
          request.unlock() // Unlock the request queue
          err.request.headers['Session-Id'] = sessionId // Add a new session to the request header
          return await request.request(err.request) // Re-complete the request
        } else {
          request.unlock()
          throw new Error(Taro.T._('Unable to get login status'), errMsg)
          return err
        }
      }
    } catch (e) {
      Taro.showToast({ title: e.message, icon: 'none' })
      throw e
    }
  },
)

export default request
Copy the code

You can wrap another layer of api.js SDK on top of this. It’s comfortable to use ~ σ ゚∀゚) ゚∀゚)σ

Use third Party statistics

Third party statistics I have used two so far, Aladdin and TalkingData. A comparison of the two shows that the community is much the same, Aladdin is more active, and TalkingData provides an API for retrieving data. However, TalkingData is not compatible with Taro. After my feedback, I received the reply that there are too many third-party development frameworks for small programs, so there is no plan to support them (´c_ ‘). Aladdin had this problem before, but it was fixed in a release a few months ago and integrated reference documentation was provided.

So if you have this kind of need, consider Aladdin ~

Unified configuration file for global style

Finally, a practice for unifying global styles. It’s simple, like creating a _scheme.scss file:

/* utils/_scheme.js */
$f1: 80px; // Arabic numeric information, such as: amount, time, etc
$f2: 40px; // Page title, such as: results, control status and other information on a single page
$f3: 36px; // Large button font
$f4: 34px; // The primary level of information, the baseline, can be continuous, such as list headers, message bubbles
$f5: 28px; // Secondary description information that serves and is associated with primary information, such as list summary
$f6: 26px; // Auxiliary information, need to weaken the content, such as: links, small buttons
$f7: 22px; // Description text, such as copyright information and other information that does not need user attention
$f8: 18px; / / is very small

$color-primary: #ff9800; // Brand color
$color-secondary: lighten($color-primary.10%);
$color-tertiary: lighten($color-primary.20%);

$color-line: #ececec; // Split line color
$color-bg: #ebebeb; / / the background color

$color-text-primary: # 000000; / / the main content
$color-text-long: # 353535; // The main content of a long paragraph
$color-text-secondary: # 888888; // Secondary content
$color-text-placeholder: #b2b2b2; / / the default value

$color-link-normal: #576b96; // Link color
$color-link-press: lighten($color-link-normal.10%);
$color-link-disable: lighten($color-link-normal.20%);

$color-complete-normal: $color-primary; // Finish the color
$color-complete-press: lighten($color-complete-normal.10%);
$color-complete-disable: lighten($color-complete-normal.20%);

$color-success-normal: #09bb07; // Success with color
$color-success-press: lighten($color-success-normal.10%);
$color-success-disable: lighten($color-success-normal.20%);

$color-error-normal: #e64340; // Wrong color
$color-error-press: lighten($color-error-normal.10%);
$color-error-disable: lighten($color-error-normal.20%);
Copy the code

Then reference the configuration file in a style file, using the corresponding variables instead of absolute values.

The word “PX” in Taro actually refers to RPX. If you want a real PX, you can capitalize px.


That’s all, not taking notes at the same time may also miss a lot of points, try to make up for it later. Writing so much is part summary, part sharing. Thank you for seeing this. If anything is wrong, you are welcome to correct me. I still have a lot to learn!

Use Taro to develop small programs, in short, or pretty cool, everyone come to use (wave ~)

٩ (´ ` omega) ۶