background
At the beginning of the preparation of small program, in order to achieve rapid development, the use of direct page introduction, each small program load is full download. Before March, the number of small program pages was stable at 35, and the download time was about 1800ms. When the new business page was launched in early April and expanded to 52 pages, the download time was basically stable at about 2300ms. With more and more user feedback, we began to prepare the small program to adopt subcontracting loading to solve this problem. After subcontracting loading in May, the number of pages remained stable at 52, but the download time was about 800ms, shortened by nearly 1500ms. Basically done 0 business invasion, add NPM script to achieve new pages, at the same time compatible subcontracting loading, page loading.
Operation process
Execute the command
npm run new
Copy the code
Gives an informational prompt and asks for a path
[Info] helps you quickly create page files. [Info] is only limited to new directory creation. [Info] Please enter the file path, has helped you omit pages/ [Info] example: address/addressList? Please enter the file path:Copy the code
A prompt to create a file after entering a path
Js [Info] created index.json [Info] created index. WXML [Info] Created index. WXSS [Info] Created !!!!Copy the code
Select main package, or subpackage, and give a prompt, if it is used in tabBar, can only select the main package, and manually configure app.json tabBar
? What package do you want to generate: (Use arrow keys) ❯ main package, write in Pages, if used in tabBar, only select main package, and manually configure tabBar subpackages for app.json, write in subpackagesCopy the code
After the option is complete, a message is displayed indicating that the creation succeeds
[Info] Write succeededCopy the code
First subcontract
First, let’s compare the unsubcontracted and subcontracted app.json code
/ / not subcontract "pages" : [" pages/a/index ", "pages/b/index", "pages/b/a list,"]Copy the code
/ / implementation of the subcontract "subpackages" : [{" root ":" pages/a ", "pages" : [" index ",]}, {" root ":" pages/b ", "pages" : [ "index", "list" ] } ]Copy the code
Next, look at the directory structure
pages
|---- a
|---- index.js
|---- b
|---- index.js
|---- list.js
app.json
Copy the code
The core business of the first subpackage is to change the common list of Pages into a binary tree structure called subpackages. The left child node is root, the right child node is Pages, and the right child node is string, and the left child node is array. Well, I can make rules. First, I can turn the pages/a/index string into an array of [‘pages’,’a’,’index’] delimited by /. Then I can slice the first two items as arguments to root, convert them to strings, and assign them to root. I assign or push all the parameters to the Pages parameter and end up with a tree that fits the requirements and pushes subpackages into it. For the data source, I just need to read the pages parameter of the app.json file and write the subpackages parameter through my method.
The main points of
- Deduplicates, because the root node is unique in subpackages, but in Pages I might intercept the original duplicate values
To solve
- The Map structure uses the intercepted root value as the key and the rest as the value. The characteristic of the Map data structure is that the key value is unique.
let list = [
"pages/a/index"."pages/a/list"."pages/a/detail/index"."pages/c/list"."pages/b/index",];let m = new Map(a);let packages = [];
list.forEach(v= >{
let arr = v.split('/');
let root = arr.splice(0.2).join('/');
let pages = arr.join('/');
if(m.get(root)){
let s = m.get(root);
m.set(root,[...s, pages]);
}else{
m.set(root,[pages]);
};
});
for(let [key,value] of m){
packages.push({
root: key,
pages: value,
})
}
console.log(packages);
Copy the code
[{root: 'pages/a', pages: ['index', 'list', 'detail/index']}, {root: 'pages/c', pages: [ 'list' ] }, { root: 'pages/b', pages: [ 'index' ] } ]Copy the code
I have realized the first subcontracting of the small program without invading the business, saving my manual labor to change. In my opinion, to solve the problem, the best solution is to use data structure, the best solution is to write compatible code, and the worst solution is to manually change. As for reading and writing files, I won’t go into that. Google will.
Instructions to create new pages
Well, I made my first subcontract. Then I wonder if I need to look at subpackages, find the corresponding root, and add Pages every time I add a page. So repetitive labor operations, why don’t I use scripts instead, right?
Core requirements
- Write interactive command input
- Check if the input page directory exists, an error is reported if it exists
- Create page directory and copy template to new page
- Depending on the consumer’s radio options, choose to write pages or subpackages
- Set the reserved folder in the Page file plus to be used for primary service splitting without adding subpackages detection
- Mask pages files in app.json and do not add subpackages detection
Design of NPM script
{
"subcontract": "node ./config/subcontract"."new": "node ./config/new"
}
Copy the code
The package.json parameter is added, ignore-files indicates files in the folder that are not detected, and pages indicates files in the tabular app.json
{
"ignore-files": [
"**/common/**"."**/component/**"."**/<name>/**"."**/<name>/**"."**/<name>/**",]."pages": [
"pages/<name>/index"."pages/<name>/index"."pages/<name>/index"]},Copy the code
Now that the requirements are clear, I’m going to find the NPM package I need
Colors Command line color inquirer Interactive command line glob global search file fs-extra file write write path path shelljs Run the shell commandCopy the code
Analyze the new.js file
const colors = require('colors');
const inquirer = require('inquirer');
const glob = require('glob');
const fs = require('fs-extra');
const path = require('path');
const shell = require('shelljs');
const PKG = require('.. /package.json');
const ROOT = path.resolve(__dirname, '.. / ');
let appJson = require('.. /app.json');
const promps = [{
type: 'input'.name: 'pagePath'.message: 'Please enter file path:'}, {type: 'list'.name: 'type'.message: 'What package do you want to generate:'.choices: [{name: 'Master package, in pages, if used in tabBar, can only select master package, and manually configure app.json tabBar'.value: '1'}, {name: 'Subpackage, write in subpackages'.value: '2',}]}];const logger = {
info(msg) {
console.log(`[Info] ${colors.green(msg)}`);
},
warn(msg) {
console.log(`[Warn] ${colors.yellow(msg)}`);
},
error(msg) {
console.log(`[Error] ${colors.red(msg)}(/ д ') / ~ ╧╧); }}; logger.info('Help you create page files quickly');
logger.info('New directory creation only');
logger.info('Please enter the file path, has omitted pages/' for you.);
logger.info('Example: XXXXX/XXXX');
Copy the code
This is the constant part of the code and the default prompt part. I wrote the Logeer object as the default color for the prompt output, and promps as the base configuration for my interactive command line. The main reason I introduced package.json is because I have shielded some files ignore-files and pages, because these two parameter folders are not detected and will not be added to subpackages.
function checkFile(name) {
const options = {
ignore: [
'**/*.js'.'**/*.wxss'.'**/*.wxml'.'**/*.json',].cwd: 'pages/'};const files = glob.sync('* *', options);
if (files.some((v) = > v === name)) {
logger.error('The entered directory already exists, terminated !!!! ');
return false;
};
return name;
};
Copy the code
This is the way to check if a file exists, simply passing in the path to check if the path exists in the pages/ directory.
function buildFile(name) {
const options = {
cwd: 'template/page'};const files = glob.sync(` ` * *, options);
files.forEach((v) = >{
const file = v.split('.tp') [0];
fs.copy(`${ROOT}/template/page/${v}`.`${ROOT}/pages/${name}/${file}`, (err) => {
if (err) {
console.error(err);
return false; }}); logger.info(` created${file}`);
});
logger.info('Created at !!!! ');
return true;
};
Copy the code
This is the way to copy the folder and copy the template files. I prepared the tempalte folder to store the template files I wrote. After creating the template file, I just copied it into it. To distinguish it from regular files, I added the.tp suffix. My template is extensible, I can add request, APP ({… }) in it, and add some of my personal methods.
function subcontract(res) {
inquirer.prompt(promps[1]).then((answers) = >{
if (answers.type === '1') {
PKG['ignore-files'].push(`${res}/ * * `);
PKG['pages'].push(`${res}/index`);
appJson['pages'].push(`pages/${res}/index`);
fs.writeFileSync(`${ROOT}/app.json`.JSON.stringify(appJson, null.2));
fs.writeFileSync(`${ROOT}/package.json`.JSON.stringify(PKG, null.2));
logger.info('Write succeeded');
};
if (answers.type === '2') shell.exec('npm run subcontract');
});
};
Copy the code
Subcontract.js is the subcontract method of the small program I wrote above. If I select Pages, I add it to the Pages object in package.json, which lists the filenames that are not detected by the Subcontract script.
async function inquirers() {
const {pagePath} = await inquirer.prompt(promps[0]);
const path = pagePath.replace(/\s+/g.' ');
if(! path) { logger.error('Input error, terminated !!!! ');
return false;
};
if (/.*[\u4e00-\u9fa5]+.*$/.test(path)) {
logger.error('Please do not enter Chinese symbols, it has been terminated !!!! ');
return false;
};
return path;
};
Copy the code
Check whether the input value is valid, whether there is Chinese, and remove Spaces
( async function() {
const inquirerRes = await inquirers();
const checkFileRes = inquirerRes && checkFile(inquirerRes);
constbuildFileRes = checkFileRes && buildFile(checkFileRes); buildFileRes && subcontract(checkFileRes); }) ();Copy the code
Finally, the inquirerRes variable determines whether the input value is correct. Then enter checkFile to check if the folder is duplicated. Call the buildFile method to create the folder and copy the template files. Finally, subcontract is called to determine whether it is a subcontract or a master package.
Subcontract. Js analysis
const glob = require('glob');
const fs = require('fs');
const path = require('path');
const colors = require('colors');
const ROOT = path.resolve(__dirname, '.. / ');
const PAG = require('.. /package.json');
let appJson = require('.. /app.json');
const ignoreFiles = PAG['ignore-files'];
const pages = PAG['pages'];
const logger = {
info(msg) {
console.log(`[Info] ${colors.green(msg)}`);
},
warn(msg) {
console.log(`[Warn] ${colors.yellow(msg)}`);
},
error(msg) {
console.log(`[Error] ${colors.red(msg)}(/ д ') / ~ ╧╧); }};const subcontract = (a)= > {
const options = {
ignore: ignoreFiles,
cwd: 'pages/'};const files = glob.sync('**/index.js', options);
let subcontractMap = new Map(a); files.forEach((v) = >{
let arr = v.split('. ') [0].split('/');
let root = arr.shift();
let page = arr.join('/');
if (subcontractMap.has(root)) {
let pages = subcontractMap.get(root);
pages.push(page);
subcontractMap.set(root, pages);
} else{ subcontractMap.set(root, [page]); }});let subcontractList = [];
subcontractMap.forEach((v, k) = >{
subcontractList.push({
root: `pages/${k}`.pages: v,
});
});
return subcontractList;
};
appJson.subpackages = subcontract();
appJson.pages = pages;
fs.writeFileSync(`${ROOT}/app.json`.JSON.stringify(appJson, null.2));
logger.info('Write succeeded');
Copy the code
This method is essentially the same as the first subcontracting method of the small program. I just changed the data source fetch. The first time I read the pages of app.json, where I read by directory, and added ignoreFiles to mask files. And some friendly tips.
At the end
There is still a long way to go to optimize the project. Why didn’t I choose subcontracting directly at first? Since there is no subcontracting at the beginning of the project, and if there is, I feel that the writing way of subcontracting mechanism may bring the possibility of mistakes to the developer. In order to shorten the project period and reduce the possibility of mistakes, I will not choose to subcontract at the beginning. When the business grows to a certain amount and tends to be stable, I can do corresponding things according to the characteristics of the business. This approach I call technology iteration. Choose a specific solution at a specific time, never over design.