一步一步踩坑實現微信小程序自動預覽

在最近的工做中,主要作的是C端的小程序,在開發過程當中卻是還好,一到改BUG的時候,可能就會進入保存,編譯,自動預覽的循環了,雖然微信已經提供了快簡介自動預覽,可是總歸是一個手動的過程,要是能監聽文件變化自動預覽不是更好嗎?javascript

查看文檔

因而我就去看了下文檔,發現微信已經提供了這個API,能夠用命令行調用,也能夠用HTTP調用html

一開始本着配置少一點的原則,我選擇了HTTP調用,由於命令行調用須要配置微信開發者工具的安裝目錄,而HTTP調用能夠經過微信指定的路徑獲取端口號(實踐證實,too young, too simple, 認真你就輸了,HTTP調用的問題太多,因此我後來又改爲了命令行調用)。vue

而後就去官方所指示的ide文件夾找端口號了,結果並無發現那個文件。怎麼回事呢?一頓搜索以後,才知道目前的版本須要以下才能開啓http服務: 微信開發者工具 -> 查看全部項目 -> 設置 -> 安全,裏面有個服務端口,選擇開啓之後就能找到ide文件,進而獲得端口號進行後續操做了。java

前置工做

  1. commander,主要是從命令行讀取參數,實現動態配置,方便調用。
  2. chalk 控制檯輸出格式多樣化。
  3. axios 用於發起http請求。
  4. package.json中的bin字段,用於實現自定義命令。
  5. 路徑區分, process.cwd()執行命令時所在的目錄,__dirname執行文件所在的目錄。 導入配置文件主要是用前者。
  6. 使用 fs.watch進行目錄和文件的監聽,在文件變更後調用自動預覽

讀取端口號

文檔中提到 端口號文件位置:node

macOS : ~/Library/Application Support/微信開發者工具/Default/.ide Windows : ~/AppData/Local/微信開發者工具/User Data/Default/.idereact

既然路徑有了,那麼就好說了,只要獲取到用戶目錄,而後再拼接上不一樣平臺的後續路徑,那麼經過讀取.ide文件就能夠獲得端口號了ios

代碼以下:git

const fs = require("fs");
const os = require("os");
const isWin = os.platform() === `win32`;

function getHttpPort() {
  const home = os.homedir();
  const suffix = isWin
    ? `/AppData/Local/微信開發者工具/User Data/Default/.ide`
    : `/Library/Application Support/微信開發者工具/Default/.ide`;
  const idePath = home + suffix;

  const port = fs.readFileSync(idePath, { encoding: "utf8" });

  return port;
}
複製代碼

打開工具或指定項目

HTTP調用

實例:github

# 打開工具
http://127.0.0.1:端口號/open
# 打開/刷新項目
http://127.0.0.1:端口號/open?projectpath=項目全路徑
複製代碼

先在瀏覽器中直接進行訪問,很好,你會發現微信開發者工具並無打開,再仔細看了下文檔,用法確定沒錯的,總共就端口號和項目路徑兩個變量,怎麼可能會出錯呢?被某廠坑久了,就知道有問題是必然的。web

算了,打不開就打不開吧,影響並非很大,反正平時開發的時候,開發者工具都是打開的。這個問題呢,我猜想是由於工具每次打開端口號都會變化,而讀取的端口號是以前的,因此就沒用了。

命令行調用

-o, --open [projectpath]: 打開工具,若是不帶 projectpath,只是打開工具。若是帶 project path,則打開路徑中的項目,每次執行都會自動編譯刷新,而且自動打開模擬器和調試器。projectpath 不能是相對路徑。項目路徑中必須含正確格式的 project.config.json 且其中有 appid 和 projectname 字段。

調用:

cli -o /Users/username/demo
複製代碼

此次的調用徹底沒有問題的。

自動預覽

HTTP調用

接口定義:

URL:/autopreview

HTTP 方法:GET

URL 參數 必填 說明
projectpath 指定路徑中的項目。如項目已打開,自動刷新項目。如項目未建立,自動建立並自動預覽項目
infooutput 指定後,會將本次自動預覽的額外信息以 json 格式輸出至指定路徑,如代碼包大小、分包大小信息。
compilecondition 指定自定義編譯條件,值爲 json 字符串,條件可指定兩個字段,pathName 表示打開的頁面,不填表示首頁,query 表示頁面參數

這個接口請求之後,卻是能夠用,可是坑也不小。

  1. infooutput 這個參數我寫了,卻並無生成相應的文件。
  2. compilecondition 這個參數實際上是我從預覽那個接口湊過來的,官方文檔只有2個參數。原本我也沒想到這個參數的,可是沒辦法,咱們的項目首頁是pages/home/index,自動預覽之後多是沒找到pages/index/index這種默認的首頁,而後就隨便跳了一個?

文檔老是缺三少四的,這裏一塊那裏一塊,稍微看漏一點這個功能可能就出不來了,說到底看文檔就是得仔細。

命令行調用

自動預覽必須處於登陸狀態,若是沒有登陸,會提示需先登陸。

--auto-preview <project_root>: 自動預覽代碼,project_root 指定項目根路徑。

--auto-preview-info-output <path>: 指定後,會將本次預覽的額外信息以 json 格式輸出至指定路徑,如代碼包大小、分包大小信息。


官方文檔只介紹了這兩個參數,仍是像以前同樣,從預覽那個的調用湊一下吧。

--compile-condition '<json>': 指定自定義編譯條件,json 條件可指定兩個字段,pathName 表示打開的頁面,不填表示首頁,query 表示頁面參數

用命令行測試:

cli --auto-preview /Users/username/demo --compile-condition {\"pathName\": \"pages/home/index\",\"query\":\"a=1\"}
複製代碼

大坑

  1. 命令行調用的時候編譯條件應該是調用JSON.stringify(obj).replace(/\"/g, `\\"`)所得到的字符串,注意要把"換成\",且先後不須要單引號,文檔裏面那種調用方式會報錯,不信的小夥伴能夠本身嘗試。
  2. 查詢參數query只能有一個參數,若是用&連接多個參數的話,提示信息是成功,可是手機並無自動預覽,害得我還納悶了很久。這個問題必須得給官方反應一下,太影響使用了。

node腳本編寫思路

輔助函數

// 將D:\\www\\soft轉換成D:/www/soft這種形式
// msg爲路徑不存在時的報錯信息
function getPath(projectpath, msg) {
  if (!projectpath) {
    throw new Error(msg);
  }
  return projectpath.split(path.sep).join(`/`);
}

// 獲取打包路徑
function getDist(config = {}) {
  const { projectpath } = config;
  return (
    config.dist ||
    (/dist\/?$/.test(projectpath) ? projectpath : (projectpath || "") + `/dist`)
  );
}

// 將exec轉爲promise類型的函數,方便使用async await
const promisify = require("util").promisify;
let { exec } = require("child_process");
exec = promisify(exec);
複製代碼

這裏介紹一下exec

child_process.exec(command[, options][, callback])
複製代碼
參數 類型 說明
command string The command to run, with space-separated arguments. (須要運行的命令,參數用空格分開)
options object { cwd: "子進程工做目錄,默認爲null", }
其餘參數我沒怎麼用過
callback 回調函數: (error: Error, stdout: string | Buffer, stderr: string | Buffer)

spawn和exec的卻別在於,spawn的輸出是實時的,而exec是執行完以後統一返回。前者還得監聽事件略顯麻煩,因此我選擇了exec。

open.js

async function open({ projectpath, cli, }) {
  return new Promise(async (resolve, reject) => {
    log();
    log(chalk.green(`打開開發者工具中...`));
    const result = await exec(
      `cli -o ${projectpath}`,
      {
        cwd: cli
      }
    );

    const isSuccess = result.stdout;
    log(isSuccess ? chalk.green(`打開成功`) : chalk.red(`打開失敗`));
    isSuccess ? resolve() : reject();
  })
}
複製代碼

preview.js

async function preview(config) {
  await open(config);

  const port = getHttpPort();
  const { dist, projectpath, time, compile, cli } = config;

  log();
  log(chalk.blue(`開始監聽文件變更`));
  log(`路徑參數: `, compile);

  fs.watch(
    dist,
    debounce(async (evt, filename) => {
      log();
      console.log(`${filename} ${evt}`);

      log(chalk.green(`自動預覽重啓中...`));
      log(
        `執行命令: cli --auto-preview ${projectpath} --compile-condition ${compile}`
      );
      const result = await exec(
        `cli --auto-preview ${projectpath} --compile-condition ${compile}`,
        {
          cwd: cli
        }
      );

      const isSuccess = result.stdout;
      log(isSuccess ? chalk.green(`自動預覽成功`) : chalk.red(`自動預覽失敗`));
    }, time || 1000)
  );
}
複製代碼

lib/index.js

const fn = {
  preview,
  upload,
};

function run(config, type) {
  if (!fn[type]) {
    throw new Error(`type參數不合法,請確保爲preview, upload的一種`);
  }

  log(chalk.green(`本次啓動類型爲: ${type}`));
  fn[type](config);
}
複製代碼

config參數從命令行參數中讀取文件得到,type參數爲命令行參數。

bin/index.js

#! /usr/bin/env node
const run = require("../lib/index");
const program = require("commander");
const fs = require("fs");
const path = require("path");
const { getPath, getHttpPort, getDist } = require("../util/index");

program
  .option("-c, --config <type>", "config file", "auto.js")
  .option("-t, --type <type>", "auto type, etc: preview, upload", "preview")
  .parse(process.argv);

// 獲取執行命令時所在的目錄,拼接上配置文件目錄,使用require(ConfigFile)便可得到相關配置
const CD = process.cwd();
const Config = program.config;
const ConfigFile = path.join(CD, Config);

const defaultCompile = {
  pathName: `pages/home/index`
};

// 判斷配置文件不存在,則直接報錯
if (!fs.existsSync(ConfigFile)) {
  throw new Error(`[ERROR]: ${Config} not found in ${CD}`);
} else {
  start();
}

function start() {
  let config = require(ConfigFile);
  config = { ...config };
  config.projectpath = getPath(
    config.projectpath,
    `配置文件中projectPath字段必須有值`
  );
  const projectpath = config.projectpath;
  config.dist = getDist(config);
  config.compile = JSON.stringify(config.compile || defaultCompile).replace(
    /\"/g, `\\"`
  );
  // 這裏可寫可不寫,打開工具後會進行再次獲取
  config.port = getHttpPort();

  run(config, program.type);
}
複製代碼

至此,該功能已經大體完成了,我已經上傳到了npm,能夠直接進行使用。

wx-auto

微信開發者工具,HTTP調用,自動預覽,提升工做效率

局部安裝

npm i wx-auto -D 或者 yarn add wx-auto -D
複製代碼

全局安裝

npm i wx-auto -g 
複製代碼

使用方法

wxauto
或者
wxauto -t preview -c auto.js
複製代碼

參數(均有默認值)

-t, --type 類型,目前支持preview和upload
-c, --config 配置文件名,默認值爲auto.js
複製代碼

文件路徑爲相對路徑,相對於執行命令時所在的路徑

配置文件

{
  cli: "D:/soft/微信web開發者工具", // cli文件所在的目錄
  projectpath: `D:/www/react/heywoof-app-frontend`, // 項目地址
  compile: {
    pathName: `pages/scene/index`, // 自動預覽的頁面路徑
    query: `activityId=5d45050569515b000c5b740a` // 查詢參數,微信目前有BUG,只能識別一個參數
  },
  build: `yarn build-test:weapp`, // 上傳以前須要執行的命令
  upload: {
    version: "1.0.1",
    desc: "測試自動上傳,不要亂動"
  }
};
複製代碼

此外還包含了自動打包上傳的功能,配置以後執行wxauto -t upload -c auto.js,上傳完畢後就會自動打開微信公衆平臺登陸的網站,我的感受仍是挺實用的。

自動化測試

在查看文檔的時候,我發現微信官方推出了一個多端統一開發工具——kbone,這個是基於vue的,就是配置略顯麻煩。

除此以外,微信小程序如今已經支持自動化測試了,感興趣的小夥伴能夠自行嘗試。注意:該功能須要最新版本支持,必定要符合文檔所說的版本,我簡單嘗試了一下,自動化是能夠實現的,更具體的測試就得看工做須要了。

相關文章
相關標籤/搜索