如何提升項目的本地構建效率?

前言

最近寫H5的項目比較多,該項目從年齡上看着還算比較年輕😂,整個架構應該是直接使用vue-cli基於vue2生成的,那底層打包工具自然也就是webpack,我們知道webpack有個通病,那就是隨着項目的不斷增大每次構建的時間也會隨之越來越長。比如我們這個項目的單次冷啓動就達到了驚人的1分20秒左右,每次跑完電腦風扇轉的飛起,簡直忍不了!(可能是電腦太老了)

下面一起看看如何將項目的冷啓動時長從1分20秒左右優化到十幾秒左右吧~

是什麼讓構建效率這麼慢?

頁面數量

由於我們這個項目是個SPA項目,路由是通過vue-auto-routing來自動生成的。爲了更直觀的看到裏面有多少個頁面,於是我把routes打印出來了。

build-1.png

居然有258個之多!頁面這麼多,webpack打包構建的速度自然就會慢。

很好奇的一點這麼多頁面都是線上在跑的?

時間都用在哪?

爲了對項目做一些有針對性的優化,我們需要了解整個編譯過程中耗時分佈,知道了各模塊的耗時數據我們才能對症下藥。

這裏可以使用speed-measure-webpack-plugin插件來進行分析。

speed-measure-webpack-plugin不僅可以分析總的打包時間,還能分析各階段loader 的耗時。

// 使用
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
const plugins = [
  // ...
  new SpeedMeasurePlugin(),
]

build-2.png

從上圖來看,編譯過程中的大部分時間都是用在vue文件的編譯處理loader上。說白了還是文件太多了導致編譯耗時比較長。

如何優化?

通用方案

開啓緩存

webpack 中幾種緩存方式:

  • cache-loader
  • hard-source-webpack-plugin
  • babel-loader 的 cacheDirectory 標誌

我們這個項目使用的vue-cli版本是4.1.0,它已經內置了 cache-loader 和 **babel-loader 的 cacheDirectory 標誌**兩種緩存

我們可以二次啓動看看

build-3.png

二次啓動花費了大概43秒,提升還是蠻大的,主要原因是 "冷啓動" 時已經將 babel-loader、vue-loader 進行了緩存:

build-4.png

另外一種緩存可自行測試 hard-source-webpack-plugin,主要緩存這種方案只在二次啓動纔能有明顯的性能提升,與我首次冷啓動就要**快**的預期不符。這種方案這裏就不再試了

開啓多線程

由於js單線程的特點,當有多個任務同時存在,它們也只能排隊串行執行。

所以有沒有可以使用類似web Worker的技術實現多線程編譯處理,將部分任務分解到多個子進程中去並行處理,子進程處理完成後把結果發送到主進程中,從而減少總的構建時間。

可選方案:

  • thread-loader(官方推出)
  • parallel-webpack
  • HappyPack

從上面可以發現,編譯過程,大部分時間都是在處理vue文件,所以可以針對vue-loader使用thread-loader

{
  test: /\.vue$/,
    include: path.resolve('src'),
      use: [
        {
          loader: 'thread-loader',
          options: {
            workers: 2,
          },
        },
      ],
},

注意:僅在耗時的操作中使用 thread-loader,否則使用 thread-loader 會後可能會導致項目構建時間變得更長,因爲每個 worker 都是一個獨立的 node 進程,創建worker的過程也是耗時的,儘量不要得不償失。

此時的編譯時間爲41秒左右,提升好像並不是特別明顯,可能在大型項目中才會發揮出更大的作用。

build-5.png

當然還有很多方案可以一一嘗試,但我覺得達到的效果應該都不會超過下面這個針對性方案。

針對性方案

該方案其實就是縮小我們的構建目標,整個項目雖然有很多頁面,從上面路由來看多達258個,但我們平時在開發過程中其實只關注我們當前需要修改的頁面,所以有沒有可能在開發過程中,我只構建我需要用的頁面,對於那些不需要的頁面不參與構建,這樣的話肯定能夠大幅提升我們的本地構建時間。

這裏還需要考慮的是,怎麼對原有構建代碼的侵入性做到最小?

思路

  • 新增構建腳本,原有npm run dev保持不變
  • 處理需要啓動的頁面,生成對應的路由routes.dev.js
  • 把原有routes提取成文件routes.pro.js
  • 再通過NormalModuleReplacementPlugin插件在編譯過程進行文件替換
  • 最後再進行構建

構建腳本

新增start命令

// package.json
"start": "node ./build/cli.js start",

主要構建代碼如下

// cli.js
const shell = require('shelljs')
const path = require('path')
const fs = require('fs')
const action = process.argv[2]
const arg = process.argv.slice(3)
let appName = arg[0]  // 指的是你要啓動的項目(文件夾名)
// const startPath = arg.join('/')
console.log('🚀🚀------start------🚀🚀')
;(() => {
  if (!appName) {
    // 未輸入項目名稱則開啓交互命令行
    openInquirer()
    return
  }
  // 啓動
  if (action === 'start') {
    start()
  }
})()

function start() {
  // console.log('啓動項目')
  process.env.action = 'signle'
  runTask(appName)
}
// 啓動項目
async function runTask(appName) {
  const cmds = []
  console.log(`🚢【啓動項目】${appName}`)
  generateRoute(appName) // 生成需要啓動的路由

  const runProPath = path.resolve(__dirname, `../src/pages/${appName}`)

  // if (process.platform === 'win32') {
  //   cmds.push(`set runProPath=${runProPath}`)
  // } else {
  //   cmds.push(`export runProPath=${runProPath}`)
  // }
  // 檢測項目是否存在
  const res = await getProject(runProPath)
  if (res.errno < 0) {
    // 拋出異常
    throw new Error('沒有找到可啓動的項目😭')
  } else {
    cmds.push(`npx vue-cli-service serve --open --colors --mode dev`)
  }

  const cmd = cmds.join(' && ')
  // return
  const { code } = shell.exec(cmd)
  return code
}

處理需要啓動的頁面

由於這個項目是用vue-auto-routing來自動生成路由的,所以這裏我依然還是用它內部的一個庫來自動生成

const { generateRoutes } = require('vue-route-generator')

// 處理需要啓動的路由
function generateRoute() {
  console.log('--', path.resolve(__dirname, `../src/pages/${appName}/`))
  const code = generateRoutes({
    pages: path.resolve(__dirname, `../src/pages/${appName}/`),
    importPrefix: `@/pages/${appName}/`,
  })

  fs.writeFileSync(path.resolve(__dirname, `../src/routes.dev.js`), code)
}

替換需要啓動的路由

根據用戶輸入的需要啓動的文件夾名,我們爲這個文件夾內的所有文件自動成了路由文件routes.dev.js,現在需要做的是通過webpack進行替換。

new webpack.NormalModuleReplacementPlugin(
    /src\/routes.pro.js/,
    './routes.dev.js',
  ),

使用

主要的工作完成,現在可以來啓動試一試

比如:啓動某**項目

# 啓動命令 npm start + 項目名稱(文件夾名)
npm start campusArea

build-6.png

現在的啓動時間大概在15秒左右,這與你當前文件夾下的文件數量有關,文件越少啓動越快!二次啓動時間大概在10秒左右,小項目首次啓動時長大概都在10秒內

首次冷啓動時長大概節省了1min,寫代碼的時間又變多了😂

優化

可能大家都習慣了npm run devnpm start,會忘記啓動頁面的參數?

不要急,這一點也考慮進去了,有個非常強大的庫inquirer可以爲我們開啓交互式命令行。

// 未輸入項目名稱則開啓交互命令行
function openInquirer() {
  // 獲取所有可啓動目錄
  const projectList = fs.readdirSync(path.resolve(__dirname, '../src/pages'))
  // console.log('projectList', projectList)

  const promptList = [
    {
      type: 'list',
      message: '🚗請選擇啓動的目錄:',
      name: 'pro',
      choices: [...projectList],
    },
  ]
  inquirer.prompt(promptList).then((answers) => {
    console.log(answers)
    appName = answers.pro
    start()
  })
}

當你直接npm start的時候,可以讓你選擇你想要啓動的目錄:

build-7.png

結束。

原文首發地址點這裏,歡迎大家關注公衆號 「前端南玖」,如果你想進前端交流羣一起學習,請點這裏

我是南玖,我們下期見!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章