webpack多頁面內存溢出

由於本身的項目是基於vue-cli3進行開發,因此這裏只討論這種狀況下的解決辦法 在進行多頁面開發的時候,項目剛開始階段,頁面較少,編譯速度還能忍受,可是一旦頁面增長,屢次熱更新就形成了內存溢出。html

緣由

這裏須要藉助一個插件來進行性能分析webpack-bundle-analyzer,在vue.config.js中添加如下代碼vue

const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
configureWebpack: {
 plugins: [
    new BundleAnalyzerPlugin(),
 ],
}
複製代碼

下面是本身項目編譯的截圖 node

能夠看到的是 webpack把全部的頁面都進行了編譯,整體積已經達到了 18M,耗時超過1分鐘,在熱更新的時候這個體積會變得更大,從而佔據 node的運行內存,致使內存溢出。可是通常在開發的時候,咱們一次更改的頁面可能就只有幾個,因此編譯這些多餘的頁面是沒有必要的。那下面就是多種解決方案

下面就是幾種嘗試的方法,加快編譯的速度webpack

增長Node運行內存

在上面提到在熱更新的時候,熱更新的代碼會大量佔據node分配的內存,致使內存溢出。那麼第一種方式,嘗試增長node的運行內存。在Node中經過JavaScript使用內存時只能使用部份內存(64位系統下約爲1.4 GB32位系統下約爲0.7 GB)。因此無論電腦實際的運行內存是多少,Node在運行代碼編譯的時候,使用內存大小不會發生變化。這樣就可能致使由於原有的內存不夠,致使內存溢出。下面給出兩種方案git

更改cmd

node_modules/.bin/vue-cli-server.cmd把下面代碼複製上去github

@IF EXIST "%~dp0\node.exe" (
  "%~dp0\node.exe" --max_old_space_size=4096 "%~dp0\..\@vue\cli-service\bin\vue-cli-service.js" %*
) ELSE (
  @SETLOCAL
  @SET PATHEXT=%PATHEXT:;.JS;=;%
  node --max_old_space_size=4096  "%~dp0\..\@vue\cli-service\bin\vue-cli-service.js" %*
)
複製代碼

更改package.json

把啓動Node服務的更改下:web

node --max_old_space_size=4096 node_modules/@vue/cli-service/bin/vue-cli-service.js serve
複製代碼

本質上沒什麼區別,都是增長Node分配的內存,在這裏把node的運行內存提升到4g就可以讓webpack熱編譯的時候不會內存溢出。使用這種方法,確實是沒有在出現內存溢出的狀況。可是在首次啓動和熱編譯的時候,速度並無發生質的提高,首次編譯仍是達到了1分鐘這種難以忍受的速度。若是項目進一步擴大,難道咱們須要再次增長node的運行內存?chrome

過濾編譯頁面

在上面截圖中能夠看到,在編譯的時候webpack編譯了一些沒必要要的頁面,原本咱們只須要調試A頁面,可是webpack把全部的頁面都進行了編譯,某些頁面咱們可能並不須要,那這裏就提出一種思路,對須要調試的頁面進行過濾。 下面是多頁面的配置:vue-cli

// page.config.js
module.exports = {
    index: {
        entry: 'src/page/index/main.js', // 頁面入口
        template: 'public/index.html', // 頁面模板路徑
        filename: 'index.html' // 輸出文件名
        title: '頁面title',
    }
}
// vue.config.js
const pages = require('./page.config.js')
module.expors = {
    pages,
}
複製代碼

能夠看到的是傳統的方案是把多頁面的配置所有引入,進行編譯,因此這裏就提出一種解決方案,對多頁面進行過濾,獲得咱們須要的編譯頁面,下面是過濾的腳本:npm

const path = require('path');
const fs = require('fs');
const pages = require('../pages.config');

const params = JSON.parse(process.env.npm_config_argv).original;
const buildPath = params[params.length - 1].match(/[a-zA-Z0-9]+/)[0] || '';

let buildConfig = {
  pages: [],
};


if (!/(test|online|serve)/gi.test(buildPath)) {
  const configJsPath = path.resolve(__dirname, `${buildPath}.js`);

  // 若是該路徑存在
  if (fs.existsSync(configJsPath)) {
    // eslint-disable-next-line import/no-dynamic-require
    buildConfig = require(configJsPath);
  } else if (pages[buildPath]) {
    buildConfig.pages = buildPath.split(',');
  } else {
    throw new Error('該路徑不存在');
  }
} else {
  buildConfig = require('./default');
}
const buildPages = {};
buildConfig.pages.forEach((name) => {
  buildPages[name] = pages[name];
});
module.exports = buildPages;
複製代碼

這樣就能夠單獨單獨編譯咱們所須要的頁面下面是default.js的內容:

module.exports = {
    pages: ['ugcDetail']
}
複製代碼

這個文件中pages就是咱們須要編譯的文章,如今webpack就過濾了不須要編譯的頁面,下面是頁面編譯速度的截圖:

頁面的編譯速度提升了驚人的 10倍,由於須要編譯的文件少了,因此運行速度也就提升了很多。通常狀況下,一我的是負責單獨的業務線,別人的代碼咱們也不須要干涉,因此也能實現一次配置,屢次運行的效果。可是有的時候咱們也須要更改別人的代碼,不能說又加一個配置文件,下面就是藉助 webpack自帶的鉤子實現編譯指定文件。

使用webbpack-dev-serve鉤子進行單獨編譯

在上面page.config.js中能夠看到每一個單頁面都有一個入口文件,webpack藉助這些入口文件進行對每一個頁面進行單獨編譯,每一個頁面編譯後的js混合到一塊兒也就很是大了,那咱們能不能讓這些入口文件暫時變成一個空文件,若是須要編譯這個頁面,在空文件中引入須要編譯的入口文件。也就是全部的入口文件都變成了一個空的js文件,若是須要編譯這個頁面,在經過

import 入口
複製代碼

實現單獨的頁面文件編譯。那webpack是如何知道咱們須要編譯的頁面呢,在webpack-dev-serve中,有一個鉤子before,在訪問頁面的時候咱們可以拿到頁面信息的路徑,下面是實現:

// vue.config.js
const compiledPages = [];
before(app) {
      app.get('*.html', (req, res, next) => {
        const result = req.url.match(/[^/]+?(?=\.)/);
        const pageName = result && result[0];
        const pagesName = Object.keys(multiPageConfig);

        if (pageName) {
          if (pagesName.includes(pageName)) {
            if (!compiledPages.includes(pageName)) {
              const page = multiPageConfig[pageName];
              fs.writeFileSync(`dev-entries/${pageName}.js`, `import '../${page.tempEntry}'; // eslint-disable-line`);
              compiledPages.push(pageName);
            }
          } else {
            // 沒這個入口
            res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
            res.end('<p style="font-size: 50px;">不存在的入口</p>');
          }
        }
        next();
      });
    },
複製代碼

多頁面配置中如下配置,須要先把入口路徑,先緩存起來,而後置空,下面是具體實現

{
    pageName: {
        entry: entryPath,
        chunks: [array]
    }
}
const fs = require('fs');
const util = require('util');

const outputFile = util.promisify(fs.writeFile);
async function main() {
  const tasks = [];
  if (!fs.existsSync('dev-entries')) {
    fs.mkdirSync('dev-entries');
  }
  Object.keys(pages).forEach((key) => {
    const entry = `dev-entries/${key}.js`;
    pages[key].tempEntry = pages[key].entry; // 暫存真正的入口文件地址
    pages[key].entry = entry;
    tasks.push(outputFile(entry, ''));
  });
  await Promise.all(tasks);
}

if (process.env.NODE_ENV === 'development') {
  main();
}

module.exports = pages;
複製代碼

這種方法就是把全部頁面入口文件置爲空文件,雖然編譯了全部的頁面可是全部的文件都是空的,因此大大的減小了首次編譯的文件大小。

速度也從原來的 80多秒,下降到了 8s。而後當咱們訪問某個頁面的頁面,執行到 before鉤子,進行單獨編譯,速度也是很是快的。

升級html-webpack-plugin版本

多頁面出現內存溢出的問題是由於在編譯的時候,實際是一次更改,編譯了多個文件,這是html-webpack-plugin的問題。由於沒生成一個頁面,就須要調用一下new htmlWebpackPlugin(),多個頁面的時候內存就不夠用了。因此改一下這個這個webpack插件的版本,升級到4.0.0-beta.8這個版本。而後再vue.config.js中添加下面的配置,這樣也不會形成內存溢出。

const htmlPlugins = [];
Object.keys(multiPageConfig).forEach((key) => {
    htmlPlugins.push(multiPageConfig[key])
})
configureWebpack: {
    plugins: [
      ...htmlPlugins,
    ],
}
複製代碼

其餘加快編譯的技巧

webpack的插件仍是很方便的,網上有啥happypack相似的插件。因爲運行在 Node.js 之上的 Webpack 是單線程模型的,因此Webpack須要處理的事情須要一件一件的作,不能多件事一塊兒作。 咱們須要Webpack能同一時間處理多個任務,發揮多核CPU電腦的威力,HappyPack就能讓Webpack 作到這點,它把任務分解給多個子進程去併發的執行,子進程處理完後再把結果發送給主進程。多是我電腦太爛了,裝上沒啥太大的提高,具體使用方法能夠參照這篇文章webpack優化之HappyPack 實戰。還有一些細節的地方好比說有些包須要加入編譯,可是通常咱們在調試的時候只須要在chrome上進行調試,開發環境就不用加入編譯,多處使用的代碼單獨打包,這些也就不說了,你們多多嘗試

這幾種解決多頁面內存溢出的方法各有優缺點,讀者可根據本身的項目自行決定使用哪一種方法,可能有時還須要多種方式組合使用,就看看那個好使好用了。
推銷一波本身的github最近在抓緊學習,會持續更新文章,但願你們多多關注。

相關文章
相關標籤/搜索