Webpack многостраничное переполнение памяти и медленная одностраничная компиляция

JavaScript

Потому что собственный проект основан наvue-cli3разработки, поэтому здесь обсуждается только решение в этом случае При многостраничной разработке в начале проекта страниц мало, и скорость компиляции можно терпеть, но как только количество страниц увеличится, многократные горячие обновления вызовут переполнение памяти.

причина

Здесь нужно использовать плагин для анализа производительностиwebpack-bundle-analyzer,существуетvue.config.jsДобавьте следующий код в

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

Ниже приведен скриншот компиляции моего собственного проекта.

То, что можно увидеть,webpackВсе страницы составлены, и общий объем достиг18M, это занимает более 1 минуты, и этот объем будет увеличиваться при горячем обновлении, занимая таким образомnodeоперативной памяти, что приводит к переполнению памяти. Но обычно во время разработки мы можем изменять только несколько страниц за раз, поэтому нет необходимости компилировать эти избыточные страницы. Тогда есть разные решения

Вот несколько способов ускорить компиляцию

УвеличиватьNodeоперативная память

Как было сказано выше, при горячих обновлениях код горячего обновления будет занимать большое количествоnodeВыделенная память, что приводит к нехватке памяти. Тогда первый способ, попробуйте увеличитьnodeоперативной памяти. существуетNodeпрошедшийJavaScriptПри использовании памяти может использоваться только часть памяти (64Под разрядной системой подразумевается1.4 GB,32Под разрядной системой подразумевается0.7 GB). Таким образом, независимо от того, сколько памяти на самом деле использует компьютер,NodeПри компиляции кода объем используемой памяти не меняется. Это может привести к переполнению памяти, поскольку исходной памяти недостаточно. Ниже приведены два варианта

Изменятьcmd

существуетnode_modules/.bin/vue-cli-server.cmdСкопируйте код ниже

@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В рамках сервисных изменений:

node --max_old_space_size=4096 node_modules/@vue/cli-service/bin/vue-cli-service.js serve

По сути разницы нет, это все прибавкаNodeВыделенная память, поместите сюдаnodeОперативная память увеличена до4gможет сделатьwebpackПри горячей компиляции нет переполнения памяти. При использовании этого метода переполнения памяти действительно не происходит. Однако при первом запуске и горячей компиляции скорость качественно не улучшилась, а первая компиляция все равно достигала невыносимой скорости в 1 минуту. Если проект будет расширяться, нужно ли нам снова увеличивать его?nodeоперативной памяти?

Фильтровать скомпилированные страницы

Как видно на скриншоте выше, при компиляцииwebpackСкомпилил несколько ненужных страниц, изначально нам нужно было только отладитьAстраница, ноwebpackВсе страницы уже скомпилированы, а некоторые страницы могут нам и не понадобиться, поэтому есть идея отфильтровать страницы, которые нужно отлаживать. Ниже приведена конфигурация многостраничного:

// 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,
}

Видно, что традиционное решение состоит в том, чтобы ввести все многостраничные конфигурации и скомпилировать их, поэтому вот решение для фильтрации нескольких страниц, чтобы получить нужные нам скомпилированные страницы.Ниже приведен отфильтрованный скрипт:

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Хуки, собранные отдельно, тоже очень быстрые.

Оптимизация скриптов

Для оптимизации приведенного выше кода, хотя все записи в файле фактически сделаны пустыми, но на самом деле этот пустой файл запакован, так что теперь смысл оптимизации в том, что пока страница не отлажена, мы не будем ее запаковывать, Еще одно преимущество этот метод заключается в том, что в целом есть тестовая среда.Для частных лиц часто необходимо развернуть этот код на тестовой машине. Вышеупомянутый способ решает проблему медленной компиляции в среде разработки, но если вы хотите залить код на тестовую машину, то он вообще не поможет, нужно загружать все файлы подкачки. Итак, учитывая следующий метод оптимизации, на этот раз мы не меняемpage.config.jsкод.
существуетscriptСоздайте папку подjsonФайл содержит самую простую запись страницы, эту страницу лучше запаковать без всего. Например:

{
  "miguRead": {
    "entry": "src/pages/miguRead/main.js",
    "template": "public/index.html",
    "filename": "miguRead.html",
    "title": "咪咕作业帮悦读会",
    "business": "base"
  }
}

затем изменитьvue.config.js

const developemntPages = require('./help')
let compiledPages;
if (process.env.NODE_ENV === 'development') {
  compiledPages = developmentPages;
  compiledPages.miguRead = multiPageConfig.miguRead;
} else {
  compiledPages = multiPageConfig;
}
before: (app) {
    //...
    if (pageName) {
        if (multiPageConfig[pageName]) {
            if (!compiledPages[pageName]) {
              compiledPages[pageName] = multiPageConfig[pageName];
              fs.writeFileSync('help.json', `
                ${JSON.stringify(compiledPages)}
              `);
            }
        } else {
            res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
            res.end('<p style="font-size: 50px;">不存在的入口</p>');
        }
    }

}

Поскольку каждый раз при перезапуске службы эти переменные также будут сбрасываться, поэтому требуется файл, который не будет изменен без перезапуска, поэтому здесь представлен файл.help.json. Наконец вpackage.jsonдобавить мониторингhelp.jsonЭта программа, когда мы посещаем страницу, будет записывать информацию о страницеhelp.json. После перезапуска службы вы можете получить информацию на этой странице.package.jsonИнформация изменена в

nodemon --watch vue.config.js --watch help.json --exec node --max_old_space_size=4096 node_modules/@vue/cli-service/bin/vue-cli-service.js serve

Обновить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,
    ],
}

Горячая компиляция одной страницы слишком медленная

Использование стека серверных технологийvue-cli3, потому что фоновый проект собирает фоновые службы отдела и использует одностраничную схему маршрутизации с несколькими маршрутами, поэтому маршрутов много. Когда маршрутов в начале было мало, скорость компиляции была довольно высокой, а когда страница увеличилась до100Когда дело доходит до первого раза, скорость просто невыносимая, медленнее, чем многостраничник в стойке регистрации, и запуск занимает несколько минут. Вот и нашел решение в интернете.Необходимо синхронно внедрять все асинхронные компоненты в среду разработки.Не знаю зачем это делается.Заинтересованные партнеры могут изучить.

решение

Установитьbabel-plugin-dynamic-import-node. Одна вещь, которую он делает, это то, что всеimport()Перевести вrequire(), так что вы можете использовать этот подключаемый модуль для синхронного представления всех асинхронных компонентов и объединения их сBABEL_ENVэтоbabelПеременные среды, чтобы он работал только в среде разработки. Конкретный метод заключается в следующем, вbabel.config.jsПрисоединяйся

env: {
    development: {
      plugins: ['dynamic-import-node'],
    },
  },

vue-cli3соответствующийenvфайл, например мой.env.development, а затем добавьте переменную среды

VUE_CLI_BABEL_TRANSPILE_MODULES=true

Хорошо, все готово, скорость компиляции увеличилась более чем в 15 раз, давайте попробуем.

Эти методы решения проблемы многостраничного переполнения памяти имеют свои преимущества и недостатки. Читатели могут решить, какой метод использовать в соответствии со своими собственными проектами, и иногда им может понадобиться использовать комбинацию методов. Просто посмотрите, какой из них прост в использовании.
Продвигайте собственную волнуgithubЯ усердно учился в последнее время, и я буду продолжать обновлять статью.Я надеюсь, что вы обратите больше внимания.

Log:

11-13: Оптимизируйте решение для многостраничной упаковки, чтобы решить проблему медленной компиляции нескольких маршрутов.