Оптимизация скорости сборки Webpack для библиотек компонентов
задний план
Основная работа в компании это разработка библиотеки компонентов (библиотека компонентов ui на основе vue, похожая на element-ui).Прошло более двух месяцев.В этот период я всегда чувствовал, что разработка и построение проект был слишком медленным.40sВокруг, я не могу этого вынести. До и после были опробованы различные методы оптимизации, но они не были идеальными. Наконец, сегодня, найдите проблему, постройте улучшение скорости50%выше, теперь просто нужно17sВокруг все настроение лучше. Теперь запишите различные используемые методы оптимизации.Поскольку это среда разработки, учитывается только скорость построения.
Оптимизация различных элементов конфигурации
В основном для добавления загрузчиковinclude
exclude
Такие мелкие оптимизации, по сути, особого прироста производительности это не приносит, только некоторое удобство.
Представьте счастливый пакет
Я видел похожие статьи раньшеhappypack
Использование многопоточной обработки может значительно повысить скорость построения проекта. Хм, я думаю, это правдоподобно. Взгляните на гитхабДокументация, попробуйте воду сейчас.
Измените некоторые конфигурации загрузчика webpack и используйте happypack
// config.dev.js
{
// ...
module: {
rules: [{
test: /\.vue$/,
loader: 'vue-loader',
options: {
css: 'style-loader!css-loader!sass-loader',
// vue文件中基本不存在css代码,所以只把js交给happypack处理
js: 'happypack/loader?id=babel'
}
}, {
test: /\.js$/,
use: 'happypack/loader?id=babel',
exclude: /node_modules/,
// components目录是组件,examples目录主要是markdown文档,test目录是单元测试
include: [utils.resolve('./components'), utils.resolve('./examples'), utils.resolve('./test')]
}, {
test: /\.scss$/,
use: 'happypack/loader?id=scss'
}]
},
plugins: [
new HappyPack({
id: 'babel',
threads: 4,
loaders: ['babel-loader']
}),
new HappyPack({
id: 'scss',
threads: 4,
loaders: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
config: {
path: utils.resolve('./postcss.config.js')
}
}
},
'sass-loader'
]
})
]
// ...
}
Здесь различные файлы, которые необходимо обработать в библиотеке компонентов, обрабатываются happypack, за исключением приведенного выше.js
scss
vue
Кроме того, такжеmd
(vue-markdown-loader) и так далее, конфигурация аналогична, поэтому не будет указана.
хорошо, конфигурация завершена, поторопитесь и протестируйте воду. Результат - ошибка... о нет! Прочтите этоофициальная документацияОписание, не поддерживаетсяvue-markdown-loader
. Хорошо сказаноmd
Измените обработку файла обратно и запустите снова. Ну, в этот раз я побежал, но времени было мало.4s-5s
Слева и справа, эммммм, не так много, как ожидалось.
Добавление --progress к работающей команде можно найти, основная трудоемкость - обработкаmd
файл, понятно, что как только вы столкнетесьmd
Биение индикатора выполнения файла замедляется.
Знайте, основной целью оптимизации должна быть обработка файлов md.
нашел этоbuild/util.js
Часть обработки внутри, часть кода выглядит следующим образом
function render(tokens, idx) {
// tokens是markdown-it parse后的结果
var m = tokens[idx].info.trim().match(/^demo\s*(.*)$/);
if (tokens[idx].nesting === 1) {
let index = idx + 1;
var html = '';
var style = '';
var script = '';
while (tokens[index].nesting === 0) {
const content = tokens[index].content;
const tag = tokens[index].info;
if (tag === 'html') {
html = convert(striptags.strip(content, ['script', 'style'])).replace(
/(<[^>]*)=""(?=.*>)/g,
'$1'
);
script = striptags.fetch(content, 'script');
style = striptags.fetch(content, 'style');
} else if (tag === 'js' && !script) {
script = striptags.fetch(content, 'script');
} else if (
['css', 'style', 'scss'].indexOf(tag) !== -1 &&
!style
) {
style = striptags.fetch(content, 'style');
}
index++;
}
var description = m && m.length > 1 ? m[1] : '';
var jsfiddle = { html: html, script: script, style: style };
var descriptionHTML = description ? md.render(description) : '';
jsfiddle = md.utils.escapeHtml(JSON.stringify(jsfiddle));
return `
<demo-block class="demo-box" :jsfiddle="${jsfiddle}">
<div class="source" slot="source">${html}</div>
${descriptionHTML}
<div class="hljs highlight" slot="highlight">
`;
}
return '</div></demo-block>\n';
}
в основном будетtip
положить в указанноеcontainer
внутри. и извлечьtokens
некоторые отмечены какhtml
js
css
код составляет объектjsfiddle
, перешел кvue组件
, предоставлятьjsbin
функция онлайн-отладки. использоватьmarkdown-it
изrender
метод, отображать другие документы, написанные в синтаксисе уценки, в html-код и помещать их в указанный div,html
Код (собственно пример кода в документации) распространяется в виде слота к вышеупомянутомуvue组件
.
На самом деле нет никакого способа оптимизировать его.
импортировать DLL
Другой способ попробовать - использовать веб-пакетDllPlugin
а такжеDllReferencePlugin
Введите dll, чтобы некоторый код, который не будет изменяться, сначала был упакован в статические ресурсы, чтобыwebpack
обрабатывать меньше
Конфигурация упакованной dll
// config.dll.js
module.exports = merge(base, {
// ...
entry: {
vendor: ['vue', 'vue-router', 'vue-i18n', 'clipboard']
},
output: {
path: path.resolve(__dirname, './dll'),
filename: '[name].js',
library: '[name]_[hash]'
},
plugins: [
new webpack.DllPlugin({
name: '[name]_[hash]',
path: path.resolve(__dirname, './dll/vendor.manifest.json')
})
]
// ...
})
Вышеупомянутый пакет конфигурации будет вbuild
создается в каталогеdll
каталог, в которомvendor.dll.js
а такжеvendor.manifest.json
затем вconfig.dev.js
в, импортDllReferencePlugin
, Это оно
Конфигурация плагина DllReferencePlugin
{
plugins: [
new webpack.DllReferencePlugin({
manifest: require('./dll/vendor.manifest.json')
})
]
}
Таким образом, в проектеwebpack
иметь дело сvue
vue-router
vue-i18n
clipboard
не пойдетnode_modules
Я получил это, я буду использовать его непосредственноvendor.js
бежать сноваnpm run dev
Это только время, чтобы узнать1s
(думаю, это на самом деле небольшое колебание времени... ничуть не меньше) Ведь большой головы здесь нет.
Однокомпонентная модель разработки
Позже я вдруг подумал, что кажется, что каждый раз, когда я разрабатываю компонент, он не один, в этом случае я обрабатываю только md-файл указанного компонента, и скорость не увеличится.
Ну, это может быть способ, попробуйте воду
найти импортmd
файл, то естьexamples/route.js
, часть кода выглядит следующим образом
function loadDocs(path) {
return r => require.ensure([],
() => r(require(`./docs${path}.md`))
);
}
Этоvue-router
Динамическая загрузка , ну, пока я пишу путь как фиксированный путь (здесь на самом деле '/' + имя компонента), не может быть достигнута?
Запущенная команда выглядит так
// package.json
{
"scripts": {
"dev:component": "cross-env RUN_ENV=component node build/dev-server.js",
}
}
Поскольку с помощью командной строкиwebpack-dev-server
Нет возможности передать параметрыprocess.argv
, так что здесь мы используемwebpack-hot-middleware
// build/dev-server.js
const webpack = require('webpack');
const webpackConfig = require('./config.dev');
const express = require('express');
webpackConfig.plugins = webpackConfig.plugins || [];
// 全局开发模式采用webpack-dev-server 无需配置hmr,这里需要单独给上
webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
webpackConfig.entry.push('webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000');
const compiler = webpack(webpackConfig);
const hotMiddleware = require('webpack-hot-middleware')(compiler, {
log: false
});
const devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
quiet: true,
logLevel: 'silent'
});
const app = express();
app.use(hotMiddleware);
app.use(devMiddleware);
app.use('/build', express.static('./build'));
app.listen(webpackConfig.devServer.port || 8089, '127.0.0.1', () => {
console.log('Starting server on http://localhost:8089');
});
Затем используйте веб-пакетDefinePlugin
Это делается путем динамического написания имени компонента.Общая идея такова. Часть реализации выглядит следующим образом:
const component = process.argv[2];
// 先判断一下是不是单组件开发模式,是的话,必须指定运行的组件
if (process.env.RUN_ENV === 'component' && !component) {
throw new Error('component is required, like: npm run dev:component slider');
}
// 然后通过DefinePlugin写入
// 对了这里有个要注意的点,path是个变量,不是字符串,所以不能是"'path'",真tm机智。
// config.dev.js
{
// ...
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: "'development'",
RUN_ENV: process.env.RUN_ENV === 'component' ? "'component'" : "''",
component: process.env.RUN_ENV === 'component' ? JSON.stringify('/' + component) : 'path'
}
})
]
// ...
}
// 然后再把 `route.js` 的源码改下
function loadDocs(path) {
return r => require.ensure([],
() => r(require(`./docs${process.env.path}.md`))
);
}
Все готово, бежим
> DONE Compiled successfully in 10792ms
Неплохо, это занимает всего 10 секунд, откройте браузер, чтобы увидеть, нет проблем. Да, Неплохо.
Отключи сервис и попробуй оригиналdev
Команда в порядке?Ну терминал в порядке,но браузер выдает ошибку???(чёрный вопросительный знак)
Кажетсяwebpack
Невозможно нормально обрабатывать и, наконец, изменить на следующее, чтобы он мог нормально работать
function loadDocs(path) {
return r => require.ensure([],
() => {
if (process.env.RUN_ENV === 'component') {
r(require(`./docs${process.env.component}.md`));
} else {
r(require(`./docs${path}.md`));
}
}
);
}
Кроме того, в дополнение к файлу md, который требует обработки только одного компонента, существует множество исходных кодов компонентов, которые не нуждаются в обработке, поэтому продолжайте модифицировать код.
При входе в приложение глобальное внедрение библиотеки ui заменено на запрос по требованию.
// 原来的代码
import Vue from 'vue'
import gsui from 'components'
// ...
Vue.use(gsui)
// 修改后的
import Vue from 'vue'
// ...
if (process.env.RUN_ENV === 'component') {
// 一些页面共用的组件
// 只能用require 不能import 因为是静态处理
Vue.use(require(`components/submenu`).default);
Vue.use(require(`components/menu`).default);
Vue.use(require(`components/layout`).default);
Vue.use(require(`components/menu-item`).default);
Vue.use(require(`components/header`).default);
Vue.use(require(`components/icon`).default);
Vue.use(require(`components/tooltip`).default);
Vue.use(require(`components/modal`).default);
Vue.use(require(`components/message`).default);
Vue.use(require(`components${process.env.component}`).default);
} else {
// 不是单组件开发模式引入全部
Vue.use(require('components').default);
}
Сравнение оптимизированного режима однокомпонентной разработки и режима глобальной разработки
Но это быстро стало непрактичным, потому что есть много компонентов, которым нужно зависеть от других компонентов, и иногда нужно читать документацию других компонентов, а однокомпонентный режим этого сделать не может.
Просто найди другой способ
Случайно обнаружил, что это оказалось потреблением производительности из-за версии vue-loader
Я не знаю, где я нашел библиотеку пользовательского интерфейса позавчераat-ui, подсознательно зашел и посмотрел их конфигурацию сборки, и обнаружил, что она очень похожа на нашу (на самом деле конфигурация вебпака тоже похожа), и она же использоваласьvue-markdown-loader
, из любопытства, клонируйте и соберите его локально. Неожиданно оказалось, что их сборка требовала только16s 16s 16sНасколько хуже, посмотрел их документы, или китайский и английский двойные (в нашей компонентной библиотеке временно нет английских документов), хотя компонент не наш много, но документов точно больше десятка, и расход когда не еще и на файловый разбор мд делать (снова лицом к знаку вопроса). Внимательно читайте свои конфигурационные файлы и обработку md, действительно обработка md кода в файле будет гораздо меньше, но это из-за разной поддержки формулировок, но не настолько, чтобы вызвать разницу во времени.
Не могу понять почему, просто попробуйте собрать наш проект с их конфигурацией. Полностью скопируйте каталог сборки и измените небольшую конфигурацию, напримерentry
alias
, установите некоторые зависимости, которых здесь нет, а остальные в принципе не нужно двигать.. Короче, запускайте и смотрите.
После модификации нескольких отчетов об ошибках он запустился, но время все равно не изменилось (37с), что странно. Попробуйте другой и запустите его проект с нашей конфигурацией.
поставить свой проектsrc
а такжеdocs
Каталог копируется, и мы также модифицируем некоторые конфигурации нашей конфигурации.entry
alias
Добавьте еще несколько загрузчиков, с ними нужно разобратьсяyml
файл, запускай и смотри. Результат еще больше озадачил, время 40с (опять рожа вопросительный знак). Наконец, в нашем проекте используйте их конфигурацию для запуска своего проекта, я хочу проверить одну вещь,Может ли это быть вызвано другой версией зависимости?, дело оказалось...
Следующий шаг — выяснить, какая зависимость приведена, вот на что следует обратить внимание.package.json
Зависимые версии, такие как^1.0.0
,к^
Зависимости в начале всегда будут устанавливаться в соответствии с последней версией под этой основной версией, то есть^1.0.0
^1.1.0
все притворяются1.x
последняя версия ниже. а также^1.0.0
а также^2.0.0
Это отличается. В конце концов, несколько различных зависимостей версий, которые в основном были опробованы, в основном:webpack
(2.х и 3.х)vue-markdown-loader
(1.x и 2.x), но после замены этих двух он все еще очень медленный Наконец, по напоминанию моих коллег, это может бытьvue-loader
потому чтоvue-markdown-loader
зависитvue-loader
, и будь то1.x
еще2.x
Все используют vue-loader12.x
версия, и мы используем13.x
В конце концов, тяжелая работа окупается, это отvue-loader
изv13.1.0
Сначала скорость сборки будет медленнее.
причина медлительности
Следующий результат был обнаружен экспертом в компании
Наконец было обнаружено, чтоv13.1.0
Надvue-loader
использоватьprettier
отформатировать код, заменив оригинальныйjs-beautify
, это и вызвало проблему с производительностью.
окончательная конфигурация
// config.base.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
config: {
path: utils.resolve('./postcss.config.js')
}
}
}
]
},
{
test: /\.md$/,
loader: 'vue-markdown-loader',
options: {
use: [
utils.mdAnchor,
utils.demoContainer,
utils.tipContainer
],
preprocess: utils.mdPreprocess
}
},
{
test: /\.scss$/,
use: 'happypack/loader?id=scss'
},
{
test: /\.jsx?$/,
exclude: exclude: [/node_modules/, /^dll$/],
use: 'happypack/loader?id=babel',
include: [utils.resolve('./components'), utils.resolve('./examples'), utils.resolve('./test')]
},
{
test: /\.json$/,
loader: 'json-loader'
},
{
test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2)(\?.*)?(#.*)?$/,
loader: 'url-loader?name=[name].[hash].[ext]'
},
{
test: /\.vue$/,
// use: 'happypack/loader?id=vue'
loader: 'vue-loader',
options: {
loaders: {
css: 'style-loader!css-loader!sass-loader',
js: 'happypack/loader?id=babel'
}
}
}
]
},
resolve: {
extensions: ['.js', '.vue', '.json', '.scss', '.css'],
alias: {
'gs-ui': utils.resolve('./'),
components: utils.resolve('./components'),
examples: utils.resolve('./examples')
}
},
plugins: [
new HappyPack({
id: 'babel',
threads: 4,
loaders: ['babel-loader']
}),
new HappyPack({
id: 'scss',
threads: 4,
loaders: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
config: {
path: utils.resolve('./postcss.config.js')
}
}
},
'sass-loader'
]
})
]
};
// config.dev.js
module.exports = merge(config, {
entry: entry,
output: {
path: '/',
publicPath: '',
filename: '[name].js'
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: "'development'",
RUN_ENV: process.env.RUN_ENV === 'component' ? "'component'" : "''",
component: process.env.RUN_ENV === 'component' ? JSON.stringify('/' + component) : 'path'
}
}),
new HtmlWebpackPlugin({
template: utils.resolve('examples/index.html'),
filename: 'index.html',
inject: true
}),
new FriendlyErrorsPlugin(),
new OpenBrowserPlugin({
url: 'http://localhost:' + PORT
}),
new webpack.DllReferencePlugin({
manifest: require('./dll/vendor.manifest.json')
})
],
devServer: {
disableHostCheck: true,
host: '0.0.0.0',
port: PORT,
quiet: true,
hot: true,
historyApiFallback: true
},
devtool: 'cheap-eval-source-map'
});
Окончательная оптимизированная скорость сборки
16-17s
, результат вполне удовлетворительный.
Эпилог
В конце концов было обнаружено, что проблема производительности сборки, вызванная зависимой версией, не может рассматриваться как оптимизация сборки веб-пакета. это яма
Другие точки оптимизации, которые можно использовать в проекте, в основном должны бытьhappypack
dll
, И может эффективно повысить скорость строительства, многие другие также должны попробовать