Окунитесь в мир веб-пакетов и станьте лучшим игроком в веб-пакетах.

внешний интерфейс JavaScript CSS Webpack

Недавно круг друзей взорвал «Первому игроку приготовиться» Спилберг, старший кинорежиссер, сконденсировал свою дань классике прошлого и свое видение будущего в этом фильме, который, можно сказать, делает аудитория зажигает.

Глядя на всю фронтенд-разработку, она постоянно развивается и быстро развивается. Front-end разработка вырезает страницы с самого начала, а инструменты автоматизированного построения front-end меняются с каждым днем.От начальных Grunt и Gulp до текущего front-end проекта можно сказать, что стандартный webpack.

Сначала отдадим должное классике:

1. Что такое веб-пакет?

Его можно рассматривать как модульный упаковщик, который анализирует структуру проекта, обрабатывает модульные зависимости и преобразует их в код, который может выполняться браузером.

  • Преобразование кода: TypeScript компилируется в JavaScript, SCSS, LESS компилируется в CSS.
  • Оптимизация файлов: сжимайте код JavaScript, CSS, HTML, сжимайте и объединяйте изображения.
  • Разделение кода: Извлеките общий код нескольких страниц, извлеките код, который не нужно выполнять на первом экране, и дайте ему возможность загружаться асинхронно.
  • Слияние модулей: в модульном проекте будет много модулей и файлов, и для классификации и объединения модулей в один файл требуется функция сборки.
  • Автоматическое обновление: отслеживайте изменения локального исходного кода, автоматически перестраивайте и обновляйте браузер.

Сборки автоматизируют серию внешнего кода для обработки сложных процессов и повышения производительности.

2. Войдите в мир веб-пакетов

Инициализировать проект

	npm install webpack webpack-cli -D

webpack4 вытаскивает webpack-cli, поэтому нам нужно скачать 2 зависимости.

После запуска Webpack рекурсивно разрешает все модули, от которых зависит Entry, из модуля, настроенного в Entry. Каждый раз при обнаружении модуля будут найдены соответствующие правила преобразования в соответствии с настроенным загрузчиком, и после преобразования модуля будет проанализирован модуль, от которого зависит текущий модуль. Эти модули сгруппированы в единицы Записи, а Запись и все зависимые от нее Модули сгруппированы в группу, которая является Чанком. Наконец, Webpack преобразует все фрагменты в выходные файлы. В течение всего процесса Webpack будет выполнять логику, определенную в плагине, в нужное время.

Webpack необходимо создать webpack.config.js в корневом каталоге проекта, чтобы экспортировать конфигурацию webpack.Конфигурация разнообразна и может быть настроена самостоятельно.Самая базовая конфигурация описана ниже.

module.exports = {
	entry: './src/index.js',
	output: {
		path: path.join(__dirname, './dist'),
		filename: 'main.js',
	}
}
  • entryПредставляет запись, webpack найдет файл для разбора
  • outputПредставляет конфигурацию входного файла
  • pathкуда поместить окончательный выходной файл
  • filenameимя выходного файла

Иногда наш проект не спа, и ему нужно сгенерировать несколько js html, тогда нам нужно настроить несколько записей.

module.exports = {
	entry: {
		pageA: './src/pageA.js',
		pageB: './src/pageB.js'
	},
	output: {
		path: path.join(__dirname, './dist'),
		filename: '[name].[hash:8].js',
	},
}

entryНастройте объект, значение ключаchunk: Блок кода, фрагмент состоит из нескольких модулей для слияния и разделения кода. посмотри на имя файла[name]: это имя относится к имени чанка, значению ключа, которое мы настроилиpageA pageB, имена упакованных файлов разные, давайте посмотрим[hash], это делается для того, чтобы присвоить выходному файлу хеш-значение, чтобы избежать кэширования, а затем:8состоит в том, чтобы взять первые 8 цифр.

У некоторых тут возникнут сомнения, проект многостраничный, должно бытьpageA.html``pageA.js``pageA.css, тогда я должен сгенерировать несколько html, это просто для того, чтобы различать запись JS, я не хочу копировать и вставлять html для каждой страницы, а html в основном повторяется, может быть, разные страницы нужно только изменитьtitle, давайте посмотрим, как решить эту проблему:

Нужно ввести веб-пакетplugin:

npm install html-webpack-plugin -D

Этот плагин может генерировать html для каждого чанка, указываяtemplate, вы можете получать параметры и использовать их в шаблонах, посмотрим, как их использовать:

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
	entry: {
		pageA: './src/pageA.js',
		pageB: './src/pageB.js'
	},
	output: {
		path: path.join(__dirname, './dist'),
		filename: '[name].[hash:8].js',
	},
	plugins: [
		 new HtmlWebpackPlugin({
            template: './src/templet.html',
            filename: 'pageA.html',
            title: 'pageA',
            chunks: ['pageA'],
            hash: true,
            minify: {
                removeAttributeQuotes: true
            }
        }),
        new HtmlWebpackPlugin({
            template: './src/templet.html',
            filename: 'pageB.html',
            title: 'pageB',
            chunks: ['pageB'],
            hash: true,
            minify: {
                removeAttributeQuotes: true
            }
        }),
	]
}

В вебпаке не указан порядок введения плагинов, который будет подробно описан далее.

  • template: Путь к HTML-шаблону.
  • filename: сгенерированное имя файла
  • title: входящие параметры
  • chunks: фрагмент для импорта
  • hash: добавьте хеш-значение в импортированный JS. Например:<script src='index.js?2f373be992fc073e2ef5'></script>
  • removeAttributeQuotes: удалить кавычки, чтобы уменьшить размер файла<script src=index.js></script>
  • специальная документация

Таким образом, страницы pageA.html и pageB.html генерируются в каталоге dist, а при настройке фрагментов тег скрипта добавляется к странице A.html для импорта страницы A.js. Так что теперь там еще css не импортируется, css нужно делать с помощью загрузчика, так что теперь нам нужно скачать несколько зависимостей, для примера возьмем scss, меньше то же самое

npm install css-loader style-loader sass-loader node-sass -D
  • css-loader: поддержка импорта в css
  • style-loader: написать css во встроенный тег стиля
  • sass-loader: преобразовать scss в css
  • node-sass: зависимость преобразования scss

Давайте посмотрим, как настроить загрузчик

module.exports = {
	module: {
        rules: [
        		{
        			test: /\.scss$/,
        			use: ['style-loader', 'css-loader', 'sass-loader'],
        			exclude: /node_modules/
        		}
        ]
    }
}
  • test: регулярное выражение, которое соответствует именам файлов
  • use: Массив, в котором размещены загрузчики, которые должны выполняться, выполняются в обратном порядке, справа налево.
  • exclude: Несоответствие файлов в node_modules

Если вы хотите использовать css как отдельный файл, вам нужно использовать для этого плагин (для веб-пакета 4.0.0 и выше требуется следующая версия):

 npm i extract-text-webpack-plugin@next -D
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
	entry: './src/index.js',
	output: {
		path: path.join(__dirname, './dist'),
		filename: 'main.js',
    },
    module: {
        rules: [
            {
                test: /\.scss$/,
                use: ExtractTextPlugin.extract({
                    // style-loader 把css直接写入html中style标签
                    fallback: 'style-loader',
                    // css-loader css中import支持
                    // loader执行顺序 从右往左执行
                    use: ['css-loader', 'sass-loader']
                }),
                exclude: /node_modules/
            }
        ]
    },
    plugins: [
        new ExtractTextPlugin('[name].[contenthash:8].css'),
    ]
}
  • Нужно добавить плагины к плагинамname: имя чанкаcontenthash:8: генерировать хеш-значение на основе содержимого и брать первые 8 цифр.
  • Изменить конфигурацию загрузчикаuse: fallback: Совместимое решение

Таким образом реализована упаковка js, html и css, так что давайте взглянем на некоторые часто используемые загрузчики:

  • babel-loader: Преобразование кода с помощью Babel
  • url-loader: зависит отfile-loader, преобразовать изображение в base64 и встроить в html, если оно превысит определенный порог, оно будет передано файло-загрузчику
	rules: [
		 // 处理js
		 {
            test: /\.js?$/,
            exclude: /node_modules/,
            use: ['babel-loader']
        },
        // 处理图片
        {
            test: /\.(png|jpg|gif|ttf|eot|woff(2)?)(\?[=a-z0-9]+)?$/,
            use: [{
                loader: 'url-loader',
                options: {
                    query: {
                        // 阈值 单位byte
                        limit: '8192',
                        name: 'images/[name]_[hash:7].[ext]',
                    }
                }
            }]
        },
	]

Конфигурация babel рекомендует создать новый файл .babelrc в корневом каталоге.

{
    "presets": [
        "env",
        "stage-0", 
        "react"
    ],
    "plugins": [
        "transform-runtime",
        "transform-decorators-legacy",
        "add-module-exports"
    ]
}
  • presets: пресет, пресет содержит несколько плагинов для удобства без ссылки на несколько плагинов.
  • env: Преобразовывать только новый синтаксис, такой как const let => .. и т. д. Не преобразовывать Iterator, Generator, Set, Maps, Proxy, Reflect, Symbol, Promise, Object.assign.
  • stage-0: правила транскодирования предложения es7 имеют 0 1 2 3 этапа 0 включает все 1 2 3
  • react: Преобразование синтаксиса реакции jsx
  • plugins: Плагины могут разрабатывать свои собственные плагины для преобразования кода (в зависимости от количества аст абстрактного синтаксиса)
  • transform-runtime: преобразование нового синтаксиса, автоматическое введение плагинов полифилла и предотвращение загрязнения глобальных переменных.
  • transform-decorators-legacy: поддерживает декораторы
  • add-module-exports: перевести экспорт по умолчанию {}, добавить module.exports = exports.default для поддержки commonjs

Поскольку мы добавляем хеш-значение к имени файла, каталог dist становится очень большим количеством файлов после многократной упаковки, и нет удаления или перезаписи.Здесь можно ввести подключаемый модуль для автоматического удаления каталога dist перед упаковкой, чтобы гарантировать, что каталог dist в настоящее время упакован.

plugins: [
	new CleanWebpackPlugin(
	    // 需要删除的文件夹或文件
	    [path.join(__dirname, './dist/*.*')],
	    {
	        // root目录
	        root: path.join(__dirname, './')
	    }
	),
]

После указания расширения вам не нужно добавлять расширение файла при запросе или импорте, он попытается добавить расширение по очереди, чтобы оно соответствовало:

resolve: {
    extensions: ['.js', '.jsx', '.scss', '.json'],
},

3. Оптимизация реальной боевой передовой техники

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

сделать публичные файлы JS

Плагин webpack.optimize.CommonsChunkPlugin устарел в webpack4 и заменен новыми элементами конфигурации.

module.exports = {
	entry: './src/index.js',
	output: {
		path: path.join(__dirname, './dist'),
		filename: 'main.js',
	},
    optimization: {
        splitChunks: {
            cacheGroups: {
                commons: {
                    chunks: 'initial',
                    minChunks: 2,
                    maxInitialRequests: 5,
                    minSize: 2,
                    name: 'common'
                }
            }
        }
    },
}

Упакуйте несколько импортированных файлов в один файл common.js

HappyPack

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

 npm i happypack -D

Необходимо преобразовать конфигурацию загрузчика, этот загрузчик обрабатывается подпроцессами

const HappyPack = require('happypack');
module.exports = {
	entry: './src/index.js',
	output: {
		path: path.join(__dirname, './dist'),
		filename: 'main.js',
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                use: 'happypack/loader?id=babel',
            },
        ]
    },
    plugins: [
        new HappyPack({
            id: 'babel',
            threads: 4,
            loaders: ['babel-loader']
        }),
    ]
}
  • id: значение идентификатора, соответствующее элементу конфигурации загрузчика
  • threads: сколько дочерних процессов настроить
  • loaders: Какой загрузчик используется для обработки
  • специальная документация

повышение масштаба

Если в вашем проекте используется синтаксис модуля ES2015 и webpack3+, то рекомендуется включить этот плагин, поместить все модули в одну функцию, уменьшить объявления функций, уменьшить размер файла и уменьшить область действия функции.

module.exports = {
	entry: './src/index.js',
	output: {
		path: path.join(__dirname, './dist'),
		filename: 'main.js',
    },
    plugins: [
        new webpack.optimize.ModuleConcatenationPlugin(),
    ]
}

Извлечь сторонние библиотеки

Удобно долго кешировать сторонние библиотеки, создавать новую запись, использовать стороннюю библиотеку как чанк и генерировать vendor.js

module.exports = {
    entry: {
        main: './src/index.js',
        vendor: ['react', 'react-dom'],
    },
}

динамическое связывание DLL

Третья библиотека не обновляется часто, и есть надежда, что она будет упакована отдельно при упаковке для повышения скорости упаковки. Для упаковки dll требуется новый файл конфигурации webpack.При упаковке dll webpack делает индекс и записывает его в файл манифеста. Затем вам нужно только прочитать файл манифеста при упаковке файла проекта.

webpack.vendor.js

const webpack = require('webpack');
const path = require('path');

module.exports = {
    entry: {
        vendor: ['react', 'react-dom'],
    },
    output: {
        path: path.join(__dirname, './dist'),
        filename: 'dll/[name]_dll.js',
        library: '_dll_[name]',
    },
    plugins: [
        new webpack.DllPlugin({
            path: path.join(__dirname, './dist/dll', 'manifest.json'),
            name: '_dll_[name]',
        }),
    ]
};

path: выходной путь файла манифестаname: имя объекта, предоставляемого dll, которое должно соответствовать output.library.context: Контекст разбора пути к пакету, он должен соответствовать пользователю dll, настроенному следующим

webpack.config.js

module.exports = {
    entry: {
        main: './src/index.js',
        vendor: ['react', 'react-dom'],
    },
    plugins: [
        new webpack.DllReferencePlugin({
            manifest: path.join(__dirname, './dist/dll', 'manifest.json')
        })
    ]
}

html

<script src="vendor_dll.js"></script>

4. Онлайн и оффлайн

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

  • webpack.base.js: общая конфигурация
  • webpack.dev.js: Конфигурация в среде разработки
  • webpack.prod.js: Конфигурация в производственной среде

Слить конфигурацию через webpack-merge, например:

среда разработки

const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge');
const base = require('./webpack.base');

const dev = {
    devServer: {
        contentBase: path.join(__dirname, '../dist'),
        port: 8080,
        host: 'localhost',
        overlay: true,
        compress: true,
        open:true,
        hot: true,
        inline: true,
        progress: true,
    },
    devtool: 'inline-source-map',
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NamedModulesPlugin(),
    ]
}
module.exports = merge(base, dev);

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

  • contentBase: статический адрес файла
  • port: номер порта
  • host: хозяин
  • overlay: если есть ошибка, отобразить ошибку в браузере
  • compress: Включить ли сжатие gzip, когда сервер возвращается в браузер
  • open: автоматически открывать браузер после завершения упаковки
  • hot: Требуется горячая замена модуляwebpack.HotModuleReplacementPluginплагин
  • inline: сборка в реальном времени
  • progress: показать процесс упаковки
  • devtool: создание сопоставления кода, просмотр предварительно скомпилированного кода, помощь в поиске ошибок.
  • webpack.NamedModulesPlugin: Показать относительный путь модуля

Производственная среда

Давайте взглянем на наиболее важные сжатие и обфускацию кода в производственной среде:

const path = require('path');
const merge = require('webpack-merge');
const WebpackParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
const base = require('./webpack.base');

const prod = {
    plugins: [
        // 文档: https://github.com/gdborton/webpack-parallel-uglify-plugin
        new WebpackParallelUglifyPlugin(
            {
                uglifyJS: {
                    mangle: false,
                    output: {
                        beautify: false,
                        comments: false
                    },
                    compress: {
                        warnings: false,
                        drop_console: true,
                        collapse_vars: true,
                        reduce_vars: true
                    }
                }
            }
        ),
    ]
}
module.exports = merge(base, prod);

webpack-parallel-uglify-pluginПараллельное сжатие кода для повышения эффективности упаковки

конфигурация uglifyJS:

  • mangle: Обфусцировать ли код
  • output.beautify: код сжимается в одну строку true для без сжатия false Compression
  • output.comments: удалить комментарий
  • compress.warnings: Не выводить предупреждение при удалении неиспользуемого кода
  • compress.drop_console: удалить консоль
  • compress.collapse_vars: использовать переменную, определенную один раз, и отменить ее определение напрямую.
  • compress.reduce_vars: объединить несколько используемых значений и определить их как переменные
  • специальная документация

5. Будьте игроком номер один

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

loader

Loader – это функция экспорта модуля. Она вызывается при успешном обычном сопоставлении. Webpack передает массив файлов, и в этом контексте можно получить доступ к API-интерфейсу загрузчика.

  • this.context: Каталог, в котором находится обрабатываемый в данный момент файл.Если файл, обрабатываемый в данный момент загрузчиком, называется /src/main.js, то this.context равен /src.
  • this.resource: полный путь запроса обрабатываемого файла, включая строку запроса, например, /src/main.js?name=1.
  • this.resourcePath: путь к обрабатываемому в данный момент файлу, например, /src/main.js.
  • this.resourceQuery: строка запроса обрабатываемого в данный момент файла.
  • this.target: равно Target в конфигурации Webpack
  • this.loadModule: Но когда загрузчик обрабатывает файл, если он зависит от результатов обработки других файлов, чтобы получить результат текущего файла, он может передать -- -- this.loadModule(request: string, callback: function(err, source, sourceMap , модуль )) для получения результата обработки файла, соответствующего запросу.
  • this.resolve: Получите полный путь к указанному файлу, подобно инструкции require, с помощью разрешения (контекст: строка, запрос: строка, обратный вызов: функция (ошибка, результат: строка)).
  • this.addDependency: добавить зависимый файл к текущему обрабатываемому файлу, чтобы при изменении зависимого файла загрузчик вызывался снова для обработки файла. Метод использования — addDependency(file:string).
  • this.addContextDependency: Аналогично addDependency, но addContextDependency добавляет весь каталог к ​​зависимостям обрабатываемого в данный момент файла. Метод использования — addContextDependency (каталог: строка).
  • this.clearDependencies: Очистить все зависимости обрабатываемого файла с помощью метода clearDependencies().
  • this.emitFile: Вывести файл, используя метод emitFile(name: string, content: Buffer|string, sourceMap: {...}).
  • this.async: возвращает функцию обратного вызова для асинхронного выполнения.

Давайте взглянемless-loaderиstyle-loaderКак достичь:

let less = require('less');
module.exports = function (source) {
    const callback = this.async();
    less.render(source, (err, result) => {
        callback(err, result.css);
    });
}
module.exports = function (source) {
    let script = (`
      let style = document.createElement("style");
      style.innerText = ${JSON.stringify(source)};
      document.head.appendChild(style);
   `);
    return script;
}

plugin

Весь процесс сборки веб-пакета имеет множество хуков, и разработчики могут добавлять свои собственные варианты поведения в процесс сборки веб-пакета на определенных этапах. Плагин состоит из следующего:

  • Именованная функция JavaScript.
  • Определите метод применения в прототипе функции плагина.
  • Указывает обработчик событий, привязанный к самому веб-пакету.
  • Обрабатывать внутренние данные конкретного экземпляра веб-пакета.
  • Обратный вызов, предоставляемый webpack, вызывается после завершения функции.

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

документация по хукам компилятора документация по хукам компиляции

Напишите небольшой плагин, который генерирует список всех связанных файлов (compiler.plugin не рекомендуется для webpack4 для регистрации плагинов, webpack5 его не поддерживает):

class FileListPlugin{
    constructor(options) {
        this.options = options;
    }
    apply(compiler) {
        compiler.hooks.emit.tap('FileListPlugin',function (compilation) {
            let fileList = 'filelist:\n\n';
            for (let filename in compilation.assets) {
                fileList += ('- '+filename+'\n');
            }
            compilation.assets['filelist.md']={
                source() {
                    return fileList;
                },
                size() {
                    return fileList.length
                }
            }
        });
    }
}
module.exports = FileListPlugin;

6. Наконец

Я все это прочитал здесь, так почему бы не поставить палец вверх?

Спасибо, что прочитали мою статью