Uni-app all page paths are configured with one pages.json, which is not conducive to maintenance and modularization management. Can we automatically construct configuration files by module partition, so that maintenance is much easier

node.js

To implement automatic builds, we first need to understand Node.js

Node is a server running environment for JavaScript.

The “runtime environment” has two meanings. First, the JavaScript language runs on the server through Node, which is a bit like a JavaScript virtual machine in this sense. Second, Node is a JavaScript library in the sense that it provides a number of libraries that allow the JavaScript language to interact with the operating system (such as reading and writing files and creating child processes).

Node uses Google’s V8 engine as the JavaScript language interpreter. Call operating system resources through the self-developed Libuv library.

Fs module

Fs is short for Filesystem. This module provides read and write capabilities for local files and is basically a simple wrapper for POSIX file operation commands. However, this module provides a choice between asynchronous and synchronous operations for almost all operations.

ReaddirSync method

The readdirSync method is used to read directories synchronously, returning an array of contained files and subdirectories

The readdirSync method takes a file path as an argument

Such as:

const fs = require('fs')
var text = fs.readdirSync('./modules')
console.log(text)
Copy the code
  • If the folder does not exist, an error is reported
  • If the folder is empty, an empty array is returned
  • The following file structure is returned[ 'home.js', 'user' ]
├ ─ modules home. Js └ ─ userCopy the code

WriteFile method

The writeFile method is used to write files

The writeFile method takes three parameters, the first is the file path, the second is the write, and the third is the callback function.

For example: var STR = “Hello Node” in home.js

var fs = require('fs'); fs.writeFile('./modules/home.js', 'var str = "Hello Node"', function (err) { if (err) throw err; Console. log(' file was written successfully '); });Copy the code

Read the content

To automate the construction of modular routes, we need to read the route file name and path in the Modules folder, export the content, and then concatenate everything into pages. Json

Read all file paths

We specify that the modules folder can only be files, and if you want to nest multiple modules, you need to build recursion

const getRouter = () => { const result = fs.readdirSync('./modules') let router = [] result.forEach(r => { const route =  require('./modules/' + r) router.push(route) }) return router }Copy the code

We delete the user folder in the Modules folder and write to home.js

Module. Exports = {baseUrl: 'pages/home/', children: [{path:' index, text: 'home page' navigationBarBackgroundColor: '#5565DB', "app-plus": {titleNView: {backButton: {title: 'return'}}}}]}Copy the code

[{baseUrl: ‘pages/home’, children: [[Object]]}]

JSON.stringify

Since the object cannot be printed directly and the data is lost, we need to use the json.stringify method to convert the content introduced by require

Modify method as follows:

const getRouter = () = > {
  const result = fs.readdirSync('./modules')
  let router = []
  result.forEach(r= > {
    const route = require('./modules/' + r)
    router.push(JSON.stringify(route))
  })
  return router
}
Copy the code

The result is as follows:

[
  '{"baseUrl":"pages/home/"."children": [{"path":"index"."text":"Home page"."navigationBarBackgroundColor":"#5565DB"."app-plus": {"titleNView": {"backButton": {"title":"Return"}}}}]}'
]
Copy the code

Now we have everything successfully retrieved

Parse the content

The configuration we wrote is quite different from the configuration required by the official, so we need to carry out corresponding analysis to get the configuration in line with uni-APP standard, check the document

Official regulations stipulate the configuration as follows:

{
  "path": "pages/home/index"."style": {
    "navigationBarTitleText": "Home page"."navigationBarBackgroundColor": "#5565DB"."app-plus": {
      "titleNView": {
        "backButton": {
          "title": "Return"
        }
      }
    }
  }
}
Copy the code

NavigationBarTitleText = “path” and “text”; navigationBarTitleText = “path” and “text”;

const buildRouter = route= > {
  const { baseUrl, children } = route
  return children.map(item= > {
    const{ path, text, ... otherConfig } = itemreturn {
      path: baseUrl + path,
      style: {
        navigationBarTitleText: text, ... otherConfig } } }) }Copy the code

The buildRouter method is an array, while the pages.json configuration is flat, so we want to concatenate all the routing arrays under the modules as follows:

const getRouter = () = > {
  const result = fs.readdirSync('./modules')
  let router = []
  result.forEach(r= > {
    const route = require('./modules/' + r)
    const routeList = buildRouter(route)
    router = router.concat(routeList)
  })
  return JSON.stringify(router)
}
Copy the code

The printed result is:

[{" path ":" pages/home/index ", "style" : {" navigationBarTitleText ":" home page ", "navigationBarBackgroundColor" : "# 5565 db," "app - plus" : { "TitleNView" : {" backButton ": {" title" : "return"}}}}}]Copy the code

After formatting, the configuration is consistent with that required by UNI-app. So far, we have completed the modular parsing of the page

Common configuration

Pages. Json requires public configuration in addition to page configuration, as follows:

{
    "easycom": {
        "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
    },
    "globalStyle": {
        "navigationBarTextStyle": "black"."navigationBarTitleText": "uni-app"."navigationBarBackgroundColor": "#F8F8F8"."backgroundColor": "#F8F8F8"}}Copy the code

Create a config.js file in the modules module directory, with the following contents:

module.exports = {
  easycom: {
    "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
  },
  globalStyle: {
    "navigationBarTextStyle": "black"."navigationBarTitleText": "uni-app"."navigationBarBackgroundColor": "#F8F8F8"."backgroundColor": "#F8F8F8"}},Copy the code

Finally, merge the common configuration information with the page path configuration information:

const config = require('./config')
constpages = { ... config,pages: getRouter()
}
Copy the code

Pages is the last configuration we need

Write content

Finally, we write the resulting configuration to pages. Json

const writeRouter = () = > {
  constpages = { ... config,pages: getRouter()
  }
  fs.writeFile(
    __dirname + '/.. /pages.json'.JSON.stringify(pages, null.' '),
    e= > e ? console.error(e) : console.log('pages. Json configuration file updated successfully '))}Copy the code

Ok, you’re done!!

Note: you need to manually execute the build.js file after each route configuration change. What? You ask how to execute, please baidu how to execute js files

Expanded function

The above content simply realizes the automatic construction of modular routing, and then we can extend some functions that meet the business requirements

Unified return button

For the return button, an APP should be unified. Our business unity sets all the return buttons as “< return”.

So according to the documentation we need to write each path in style

"App - plus" : {" titleNView ": {" backButton" : {" title ":" return "}}}Copy the code

The programmer’s first duty is lazy, can global configuration of the problem why to manually configure each page?

If the page is not configured with the title button, then we automatically configure it. If there is a configuration, we take the page’s own configuration and write the setBackChar method as follows:

const setBackChar = (otherConfig = {}, title = 'return') = > {
  if (otherConfig.hasOwnProperty('app-plus')) {
    const appPlus = otherConfig['app-plus']
    if (appPlus.hasOwnProperty('titleNView')) {
      const titleNView = appPlus.titleNView
      if (titleNView.hasOwnProperty('backButton')) {
        const backButton = titleNView.backButton
        if (backButton.hasOwnProperty('title')) {
          // The page is set to return button or empty
        } else {
          backButton.title = title
        }
      } else {
        titleNView.backButton = { title }
      }
    } else {
      appPlus.titleNView = {
        backButton: { title }
      }
    }
  } else {
    otherConfig['app-plus'] = {
      titleNView: {
        backButton: { title }
      }
    }
  }
}
Copy the code

The setBackChar method is executed when the buildRouter method executes the map loop

Short name of the configuration parameter

[navigationBarTitleText] [navigationBarTitleText] [navigationBarTitleText] [navigationBarTitleText] [navigationBarTitleText] [navigationBarTitleText

Encapsulating common configuration

The whole APP should have a unified style. If the font style of the navigation bar, background or navigation menu on the right need to be modified, it can be encapsulated as a public method to generate, for example:

// utils.js
const navRightButton = (text, type = 'none') => {
  return {
    text: text,
    type: type,
    fontSize: "16px",
    fontWeight: "bold",
    width: "40px"
  }
}
Copy the code

Route Configuration

const { navRightButton } = require('.. /utils') module.exports = {baseUrl: 'pages' /form/', children: [{path: 'index', text: 'form ', "app-plus": {titleNView: {buttons: [navRightButton(' save ')]}}},]}Copy the code

Route jump optimization

The official provided six methods for us to use, as follows:

  • NavigateTo keeps the current page and jumps to a page in the app. Use uni. NavigateBack to go back to the original page.
  • RedirectTo closes the current page and redirects to a page in the application.
  • ReLaunch closes all pages and opens to a page within the app.
  • SwitchTab Jumps to the tabBar page and closes all non-Tabbar pages.
  • NavigateBack closes the current page and returns to the previous page or multi-level page. GetCurrentPages () gets the current page stack and determines how many layers you need to return.
  • PreloadPage is a performance optimization technique for preloading pages. Preloaded pages open faster.

$router.push(‘/user-admin’); uni-app: this.$router.push(‘/user-admin’)

Plug-in development

Create router.js in the common/plugins folder

class Router {
  push() {}
}

export default {
  install(Vue, options) {
    Vue.prototype.$router = new Router()
  }
}
Copy the code

Then register in main.js

import router from '@/common/plugin/router'
Vue.use(router)
Copy the code

After that, we can do route jump in the way of VUE. Mom no longer needs to worry that I can’t remember the name of route jump API provided by UNI-app

$router.push

Methods into the reference

Since it is modeled after VUE, the input parameter form must be consistent with VUE

type name path query params object
String String String Object Object Object
Jump type Routing alias Routing path With query parameter Vuex parameters Other configuration
## jump type parsing
const navigate = (type) = > {
    let navigateMethods
    switch (type) {
      case 'go':
        navigateMethods = uni.navigateTo
        break
      case 'tab':
        navigateMethods = uni.switchTab
        break
      case 'reLaunch':
        navigateMethods = uni.reLaunch
        break
      case 'redirectTo':
        navigateMethods = uni.redirectTo
        break
      default:
        navigateMethods = uni.navigateTo
        break
    }
    return navigateMethods
}
Copy the code

The preloadPage method is missing, which is not used in my business for the time being. If necessary, it can be packaged by itself

Routing alias

To implement route alias requires a map of alias and full path, this time can use the above modular route automatic construction

First, we need to configure a name attribute when configuring a single page path, such as:

module.exports = {
  baseUrl: 'pages/home/'.children: [{path: 'index'.name: 'home'.text: 'home'.navigationBarBackgroundColor: '#5565DB',}}]Copy the code

BuildRouter: name = name; buildRouter: name = name; buildRouter: name = name;

const nameList = []
const buildRouter = route= > {
  const { baseUrl, children } = route
  return children.map(item= > {
    const{ name, path, text, ... otherConfig } = itemif(! nameList.some(item= > item.name === name)) {
      nameList.push({
        path: '/' + baseUrl + path,
        name
      })
    } else {
      console.error(`${name}Name repeated, please modify after recompiling ')}// All page back buttons are returned with Chinese characters
    setBackChar(otherConfig)
    return {
      path: baseUrl + path,
      style: {
        navigationBarTitleText: text, ... otherConfig } } }) }Copy the code

The name alias cannot be repeated. If an error message is displayed repeatedly, modify the alias

After the name list information is saved, it needs to be written to a configuration file. In this case, add aliasrouter.js to the write method

fs.writeFile( __dirname + '/aliasRouter.js', 'export default ' + JSON.stringify(nameList, null, ' '), e => e ? Console. error(e) : console.log('pages. Json Route alias list file updated successfully ')Copy the code

Push method

  1. First, if the route alias is passed in, find the full path name based on the list of route aliases
  2. Then the corresponding API in UNI can be obtained according to type
  3. Parse query into the corresponding URL parameter string (bulidURL method)
  4. Save params under the corresponding route alias (vuex)
  5. The other parameters are combined for route redirection
const push = ({ type, name, path, query, params, object }) = > {
    type = type || 'go'
    let pathFind
    if(name) pathFind = aliasRouter.find(item= > item.name === name)
    const navigateMethods = this.navigate(type)
    let url = pathFind ? pathFind.path : path
    if (query) {
      url = bulidURL(url, query)
    }
    const navigateData = {
      url,
      fail(err) {
        console.error(err)
      },
      animationType: 'pop-in'.animationDuration: 200. object }if (params) {
      if(! name) {console.error('Please jump by name')}else {
        store.dispatch('router/addParamsData', { name, params }).then(() = > {
          navigateMethods(navigateData)
        })
        return
      }
    }
    navigateMethods(navigateData)
  }
Copy the code

$router.back

When you return, if a callbackName is passed in, the callbackName method in the page component of the returned page is executed and the parameters it carries are carried in

Const back = ({callbackName, data, object}) => {if (callbackName && isObject(callbackName)) {// If the first argument is an object, Object = callbackName} else if (callbackName && Typeof callbackName === 'string') {const pages = getCurrentPages(); Const prevPage = pages[pages.length-2]; // Get all page stack instances const prevPage = pages[pages.length-2]; $vm[callbackName]) prevPage.$vm[callbackName](data)} uni. NavigateBack ({delta: 1, animationType: 'pop-out', animationDuration: 200, fail(err) { console.error(err) }, ... object }); }Copy the code

Use the sample

There is a list display page

  1. Click the button to jump to the filter page (bring in the default search criteria)
  2. After confirming the selection of screening conditions, return to the previous page and bring back the search conditions to trigger the search
<! List display page -->
<template>
   <view class="content">
       <u-button @click="goSearchPage">The query</u-button>
       <view>
         <! -- List display -->
       </view>
   </view>
</template>

<script>
export default {
 data() {
       return {
           setSearchForm: {
             name: 'Joe'}}},methods: {
   goSearchPage() {
       this.$router.push({
           name: 'search'.query: this.setSearchForm
       })
   },
   setSearchForm(form) {
       this.setSearchForm = form
       this.getList()
   },
   getList() {
     // Perform list queries based on search criteria}}}</script>
Copy the code
<! List search page -->
<template>
 <view>
   <u-button @click="goBackSearch">confirm</u-button>
 </view>
</template>

<script>
export default {
 data() {
   return {
     searchForm: {}}}.onLoad(options) {
   this.searchForm = options
 },
 methods: {
   goBackSearch() {
     this.$router.back({
       callbackName: 'setSearchForm'.data: this.searchForm
     })
   }
 }
}
</script>
Copy the code
  1. On the list page, click Query to redirect the routequeryBring the search criteria to the search page
  2. The search page gets the last page passed in parameter through the onLoad page life cycle and assigns a value tosearchForm
  3. Search page click ok to trigger the list pagesetSearchFormMethod, and put in parameterssearchForm

The complete code

import aliasRouter from '@/router/router'
import store from '@/store'

const toString = Object.prototype.toString

export function isDate (val) {
 return toString.call(val) === '[object Date]'
}

export function isObject (val) {
 returnval ! = =null && typeof val === 'object'
}

function encode (val) {
 return encodeURIComponent(val)
   .replace(/%40/g.The '@')
   .replace(/%3A/gi.':')
   .replace(/%24/g.'$')
   .replace(/%2C/gi.', ')
   .replace(/%20/g.'+')
   .replace(/%5B/gi.'[')
   .replace(/%5D/gi.'] ')}function bulidURL(url, params) {
 if(! params) {return url
 }

 const parts = []

 Object.keys(params).forEach((key) = > {
   let val = params[key]
   if (val === null || typeof val === 'undefined') {
     return
   }
   let values
   if (Array.isArray(val)) {
     values = val
     key += '[]'
   } else {
     values = [val]
   }
   values.forEach((val) = > {
     if (isDate(val)) {
       val = val.toISOString()
     } else if (isObject(val)) {
       val = JSON.stringify(val)
     }
     parts.push(`${encode(key)}=${encode(val)}`)})})let serializedParams = parts.join('&')

 if (serializedParams) {
   const markIndex = url.indexOf(The '#')
   if(markIndex ! = = -1) {
     url = url.slice(0, markIndex)
   }

   url += (url.indexOf('? ') = = = -1 ? '? ' : '&') + serializedParams
 }

 return url
}

class Router {
 navigate(type) {
   let navigateMethods
   switch (type) {
     case 'go':
       navigateMethods = uni.navigateTo
       break
     case 'tab':
       navigateMethods = uni.switchTab
       break
     case 'reLaunch':
       navigateMethods = uni.reLaunch
       break
     default:
       navigateMethods = uni.navigateTo
       break
   }
   return navigateMethods
 }
 push({ type, name, path, query, params, object }) {
   type = type || 'go'
   let pathFind
   if(name) pathFind = aliasRouter.find(item= > item.name === name)
   const navigateMethods = this.navigate(type)
   let url = pathFind ? pathFind.path : path
   if (query) {
     url = bulidURL(url, query)
   }
   const navigateData = {
     url,
     fail(err) {
       console.error(err)
     },
     animationType: 'pop-in'.animationDuration: 200. object }if (params) {
     if(! name) {console.error('Please jump by name')}else {
       store.dispatch('router/addParamsData', { name, params }).then(() = > {
         navigateMethods(navigateData)
       })
       return
     }
   }
   navigateMethods(navigateData)
 }
 back({ callbackName, data, object }) {
   if (callbackName && isObject(callbackName)) {
     // If the first argument is an object, it returns without a callback
     object = callbackName
   } else if (callbackName && typeof callbackName === 'string') {
     const pages = getCurrentPages();  // Get a list of all page stack instances
     const prevPage = pages[ pages.length - 2 ];  // Previous page instance
     if (prevPage.$vm[callbackName]) prevPage.$vm[callbackName](data)
   }
   uni.navigateBack({
     delta: 1.animationType: 'pop-out'.animationDuration: 200.fail(err) {
       console.error(err)
     },
     ...object
   });
 }
}

export default {
 install(Vue, options) {
   Vue.prototype.$routers = new Router()
 }
}

export {
 Router
}

Copy the code

End, scatter flowers!!