задний план
В последнее время компания, выполняющая реконструкцию проекта H5, включает в себя оптимизацию сборки, по некоторым историческим причинам проект изначально использовался для создания пакетов. теперь проект усложнился, способ сборки теперь занимает больше времени для каждого пакета; второй — использование самого инструмента обслуживания тоже остановился под угрозой; другой из-за этой реконструкции, а также для платформы обновления Vue1.0 до Vue2 .0, включая ряд зависимостей (vue-style-loader и т.д.) совместимость версий. Выбросьте в один прекрасный день ничего, просто обновите инструменты сборки непосредственно до webpack4, синхронизируйте с vue2 и vuex3 за один шаг.
Из-за бизнес-потребностей компании (SEO, страницы в основном основаны на доставке), наш проект принимает многостраничную структуру, шаблон одностраничного приложения на основе онлайн-vue, официальный предоставляет vue-cli, также есть много третьих- партийные, многостраничные шаблоны Ссылок не много. Я провел около двух недель до и после, обращаясь к некоторым материалам и документам блога, упорядочив этот набор шаблонов многостраничных приложений на основе webpack4 + vue2 + vuex3, записал его для моего будущего просмотра и поделился им со студентами, нуждающимися в справке. .
Основные концепции веб-пакета
Вдохновленный инструментами сборки с нулевой конфигурацией, такими как Parcel, webpack4 также работает над отсутствием конфигурации и провел большую оптимизацию.Хотя он поддерживает нулевую настройку, если вы хотите детально управлять модулями, вам все равно нужно вручную настроить некоторые элементы конфигурации. Но по сравнению с предыдущей версией вебпака он значительно упрощен, и начать работу с ним намного проще. Здесь мы сначала понимаем несколько основных элементов конфигурации webpack4, которые будут расширены один за другим позже:
- mode
- entry
- output
- loader
- plugins
- devServer
Далее я максимально подробно опишу весь процесс создания многостраничных приложений vue2 и vuex на основе webpack4 в указанном выше порядке.
mode
Новое в webpack4, укажите режим упаковки, необязательные значения:
- разработка, режим разработки
- Настройте process.env.node_env для разработки
- Включить плагины NamedChunksPlugin, NamedModulesPlugin
- производство, режим производства
- установит process.env.NODE_ENV в производство
- Включены максимальные оптимизации (сжатие модулей, конкатенация и т. д.)
- нет, этот режим не будет оптимизирован
Есть два способа установить режим:
- Задайте в виде параметров команды оболочки в package.json
webpack --mode=production
- Путем настройки элемента конфигурации режима
module.exports = {
mode: 'production'
};
Для получения дополнительной информации см.:Режим официальной документации
entry
Сравнивая многостраничные приложения и одностраничные приложения (SPA), самая большая разница — это разница во входе
- Многостраничный: наконец, несколько записей (страницы html) создаются путем упаковки.Как правило, в дополнение к общедоступным статическим файлам ( js/css ), каждый файл записи должен также содержать статические ресурсы для конкретной страницы.
- Одна страница: существует только одна запись ( index.html ), все статические файлы после пакета необходимо импортировать на страницу, а все содержимое страницы контролируется JavaScript.
Следует отметить, что вход в приведенное выше относится к файлу HTML, который окончательно упакован в каталог dist, а запись, которую мы здесь настроили, на самом деле представляет собой модуль JS, представленный HTML, эти модули JS вместе с извлеченным общедоступным JS. в конечном итоге модуль должен использовать плагин HTML-WebPack-Plugin в файле HTML:
const config = require('./config'); // 多页面的配置项
let HTMLPlugins = [];
let Entries = {};
config.HTMLDirs.forEach(item => {
let filename = `${item.page}.html`;
if (item.dir) filename = `${item.dir}/${item.page}.html`;
const htmlPlugin = new HTMLWebpackPlugin({
title: item.title, // 生成的html页面的标题
filename: filename, // 生成到dist目录下的html文件名称,支持多级目录(eg: `${item.page}/index.html`)
template: path.resolve(__dirname, `../src/template/index.html`), // 模板文件,不同入口可以根据需要设置不同模板
chunks: [item.page, 'vendor'], // html文件中需要要引入的js模块,这里的 vendor 是webpack默认配置下抽离的公共模块的名称
});
HTMLPlugins.push(htmlPlugin);
Entries[item.page] = path.resolve(__dirname, `../src/pages/${item.page}/index.js`); // 根据配置设置入口js文件
});
// ...
Информация о конфигурации для нескольких страниц в config.js:
module.exports = {
HTMLDirs: [
{
page: 'index',
title: '首页'
},
{
page: 'list',
title: '列表页',
dir: 'content' // 支持设置多级目录
},
{
page: 'detail',
title: '详情页'
}
],
// ...
};
Наконец, введите соответствующую конфигурацию:
module.exports = {
entry: Entries,
// ...
plugins: [
...HTMLPlugins // 利用 HTMLWebpackPlugin 插件合成最终页面
]
// ...
}
Извлечение общедоступных модулей будет представлено отдельно позже.
Дополнительная информация о конфигурации для html-webpack-plugin:официальный сайт html-webpack-plugin
output
Настройте файл файла экспорта и пути:
const env = process.env.BUILD_MODE.trim();
let ASSET_PATH = '/'; // dev 环境
if (env === 'prod') ASSET_PATH = '//abc.com/static/'; // build 时设置成实际使用的静态服务地址
module.exports = {
entry: Entries,
output: {
publicPath: ASSET_PATH,
filename: 'js/[name].[hash:8].js',
path: path.resolve(__dirname, '../dist'),
},
}
Здесь сгенерированный файл js повешен с 8-битным штампом MD5, чтобы в полной мере использовать кеш CDN.
Для получения информации о нескольких методах расчета и различиях хэша см.Разница между хэшем, чанкхешем и хэшем контента в веб-пакете
loader
Загрузчик используется для преобразования исходного кода модуля и отвечает за преобразование содержимого определенного формата файла в модуль, который может поддерживать веб-пакет, например, преобразование предварительной обработки sass в модули css, преобразование TypeScript в JavaScript или преобразование встроенного кода. изображения для URL-адреса данных и т. д.
Конкретная конфигурация:
- webpack.base.js (базовый файл конфигурации)
const VueLoaderPlugin = require('vue-loader/lib/plugin');
// ...
module: {
rules: [
{
test: /\.vue$/, // 处理vue模块
use: 'vue-loader',
},
{
test: /\.js$/, //处理es6语法
exclude: /node_modules/,
use: ['babel-loader'],
},
{
test: /\.(png|svg|jpg|gif)$/, // 处理图片
use: {
loader: 'file-loader', // 解决打包css文件中图片路径无法解析的问题
options: {
// 打包生成图片的名字
name: '[name].[ext]',
// 图片的生成路径
outputPath: config.imgOutputPath,
}
}
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/, // 处理字体
use: {
loader: 'file-loader',
options: {
outputPath: config.fontOutputPath,
}
}
}
]
},
plugins: [
// ...
new VueLoaderPlugin()
]
// ...
vue-loader следует использовать вместе с плагином VueLoaderPlugin. babel-loader должен использоваться с .babelrc. Здесь «этап 2» настроен на использование расширенного синтаксиса в es7.Фактически измерено, что, если он не настроен, он не может обрабатывать новые синтаксические функции, такие как расширение объекта, асинхронность и ожидание.
Конфигурация .babelrc:
{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"stage-2"
],
"plugins": ["transform-runtime"]
}
Для конфигурации, связанной с .babelrc, обратитесь к:официальная документация;Конфигурация Babel - разница между этапами каждого этапа
- webpack.dev.js (файл конфигурации разработки)
// ...
module: {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
use: [
'vue-style-loader', // 处理vue文件中的css样式
'css-loader',
'postcss-loader',
]
},
{
test: /\.scss$/,
exclude: /node_modules/,
use: [ // 这些loader会按照从右到左的顺序处理样式
'vue-style-loader',
'css-loader',
'sass-loader',
'postcss-loader',
{
loader: 'sass-resources-loader', // 将定义的sass变量、mix等统一样式打包到每个css文件中,避免在每个页面中手动手动引入
options: {
resources: path.resolve(__dirname, '../src/styles/lib/main.scss'),
}
}
]
},
{
test: /\.(js|vue)$/,
enforce: 'pre', // 强制先进行 ESLint 检查
exclude: /node_modules|lib/,
loader: 'eslint-loader',
options: {
// 启用自动修复
fix: true,
// 启用警告信息
emitWarning: true,
}
}
]
},
// ...
- webpack.prod.js (файл рабочей конфигурации)
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ASSET_PATH = '//abc.com/static/'; // 线上静态资源地址
// ...
module: {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
}, {
test: /\.scss$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader',
'postcss-loader',
{
loader: 'sass-resources-loader',
options: {
resources: path.resolve(__dirname, '../src/styles/lib/main.scss'),
},
}
]
},
{
test: /\.(png|svg|jpg|gif)$/, // 处理图片
use: {
loader: 'file-loader', // 解决打包css文件中图片路径无法解析的问题
options: {
// 打包生成图片的名字
name: '[name].[hash:8].[ext]',
// 图片的生成路径
outputPath: config.imgOutputPath,
publicPath: ASSET_PATH
}
}
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/, // 处理字体
use: {
loader: 'file-loader',
options: {
outputPath: config.fontOutputPath,
publicPath: ASSET_PATH
}
}
}
]
},
// ...
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[chunkhash:8].css' // css最终以单文件形式抽离到 dist/css目录下
})
]
Вытрите CSS в один файл. Экстракт-текстовый веб-плагин, используемый, прежде чем больше не поддерживает WebPack4, а официальный выпускmini-css-extract-pluginдля обработки извлечения css
plugins
В процессе упаковки веб-пакета работа по преобразованию кода модуля выполняется загрузчиком, а другая работа может выполняться плагином. Обычно используются:
- uglifyjs-webpack-plugin, обрабатывает сжатие кода js
- mini-css-extract-plugin, извлечь css в один файл
- clean-webpack-plugin для очистки папки dist при каждой сборке
- копировать-webpack-плагин, копировать файлы
- webpack.HotModuleReplacementPlugin, горячая перезагрузка
- webpack.DefinePlugin, определить переменные среды
Конкретная конфигурация:
- webpack.base.js (базовый файл конфигурации)
const HTMLWebpackPlugin = require('html-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
// ...
plugins: [
new VueLoaderPlugin(),
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../public'),
to: path.resolve(__dirname, '../dist'),
ignore: ['*.html']
},
{
from: path.resolve(__dirname, '../src/scripts/lib'), // 搬运本地类库资源
to: path.resolve(__dirname, '../dist')
}
]),
...HTMLPlugins, // 利用 HTMLWebpackPlugin 插件合成最终页面
new webpack.DefinePlugin({
'process.env.ASSET_PATH': JSON.stringify(ASSET_PATH) // 利用 process.env.ASSET_PATH 保证模板文件中引用正确的静态资源地址
})
]
- webpack.prod.js (файл рабочей конфигурации)
// 抽取css extract-text-webpack-plugin不再支持webpack4,官方出了mini-css-extract-plugin来处理css的抽取
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
plugins: [
// 自动清理 dist 文件夹
new CleanWebpackPlugin(['dist'], {
root: path.resolve(__dirname, '..'),
verbose: true, //开启在控制台输出信息
dry: false,
}),
new MiniCssExtractPlugin({
filename: 'css/[name].[chunkhash:8].css'
})
]
devServer
При ежедневной разработке нам необходимо запускать статический сервер локально, чтобы облегчить разработку и отладку.Мы используем официально предоставленный инструмент webpack-dev-server, чтобы быстро запустить статический сервис на основе текущей конфигурации сборки веб-пакета. Когда режим находится в стадии разработки, он будет иметь функцию горячей перезагрузки, поэтому нет необходимости вручную вводить плагин webpack.HotModuleReplacementPlugin.
Как правило, установите webpack-dev-server в качестве зависимости разработки, а затем используйте сценарии npm для запуска:
npm install webpack-dev-server -S
Конфигурация скриптов в пакете:
"scripts": {
"dev": "cross-env BUILD_MODE=dev webpack-dev-server ",
},
Для подробной настройки devServer обратитесь к официальной документации:dev-server
Конфигурация splitChunks
webpack 4 удалил CommonsChunkPlugin и заменил его двумя новыми элементами конфигурации (optimization.splitChunks и Optimization.runtimeChunk) для извлечения общих модулей js. С параметром Optimization.runtimeChunk: true веб-пакет добавит к каждой записи дополнительный фрагмент, содержащий только время выполнения (runtime). (Примечание: это необходимо использовать в зависимости от сцены, из-за чего каждая запись будет загружать дополнительную копию кода среды выполнения).
Введение в конфигурацию splitChunks по умолчанию:
module.exports = {
// ...
optimization: {
splitChunks: {
chunks: 'async', // 控制webpack选择哪些代码块用于分割(其他类型代码块按默认方式打包)。有3个可选的值:initial、async和all。
minSize: 30000, // 形成一个新代码块最小的体积
maxSize: 0,
minChunks: 1, // 在分割之前,这个代码块最小应该被引用的次数(默认配置的策略是不需要多次引用也可以被分割)
maxAsyncRequests: 5, // 按需加载的代码块,最大数量应该小于或者等于5
maxInitialRequests: 3, // 初始加载的代码块,最大数量应该小于或等于3
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: { // 将所有来自node_modules的模块分配到一个叫vendors的缓存组
test: /[\\/]node_modules[\\/]/,
priority: -10 // 缓存组的优先级(priotity)是负数,因此所有自定义缓存组都可以有比它更高优先级
},
default: {
minChunks: 2, // 所有重复引用至少两次的代码,会被分配到default的缓存组。
priority: -20, // 一个模块可以被分配到多个缓存组,优化策略会将模块分配至跟高优先级别(priority)的缓存组
reuseExistingChunk: true // 允许复用已经存在的代码块,而不是新建一个新的,需要在精确匹配到对应模块时候才会生效。
}
}
}
}
};
Для подробной настройки SplitChunksPlugin обратитесь к официальной документации:SplitChunksPlugin
Vue && Vuex
Vue:
Мы знаем, что одностраничное приложение vue имеет только одну запись, а файл записи по умолчанию — main.js, в котором шаблон vue и Vuex обрабатываются для окончательного создания объекта Vue. Многостраничное приложение имеет несколько записей, что эквивалентно обработке того, что main.js на одной странице должен делать в каждой записи. Общая конфигурация выглядит так:
import Vue from 'vue';
import Tpl from './index.vue'; // Vue模板
import store from '../../store'; // Vuex
new Vue({
store,
render: h => h(Tpl),
}).$mount('#app');
Vuex:
Чтобы избежать централизации всего состояния в объекте хранилища, что приводит к раздуванию файлов и затруднениям в обслуживании, хранилище здесь разделено на несколько модулей. Каждый модуль имеет свое состояние, мутацию и действие. При этом геттер извлекается в отдельный файл. Структура файла следующая:
|- store
| |-modules
| | |-app.js // 单个module
| | |-user.js // // 单个module
| |-getters.js
| |-index.js // 在这里组织各个module
Настройки для одного модуля следующие:
const app = {
state: { // state
count: 0
},
mutations: { // mutations
ADD_COUNT: (state, payload) => {
state.count += payload.amount;
}
},
actions: { // actions
addCount: ({ commit }, payload) => {
commit('ADD_COUNT', {
amount: payload.num
});
}
}
};
export default app;
Наконец, соберите каждый модуль в index.js:
import Vue from 'vue';
import Vuex from 'vuex';
import app from './modules/app';
import user from './modules/user';
import getters from './getters';
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
app,
user
},
getters
});
export default store;
Суммировать
Я, наконец, закончил писать и заполнил множество пробелов в середине, но у меня еще много достижений на этом пути, и я продолжу улучшать его, когда будет время. Адрес github исходного кода проекта находится здесь:webpack4-vue2-multiPage, если вам это нужно, примите это напрямую. Если это поможет вам, пожалуйста, дайте ему звезду ~~
использованная литература
- Официальная документация WEPACK
- Руководство по неполной миграции Webpack4
- Многостраничное прикладное решение на основе Webpack4 + Vue
- Что бы мы делали с субподрядом без CommonsChunkPlugin?
- Разница между хэшем, чанкхешем и хэшем контента в веб-пакете
- webpack 4, наконец, знает, что «соглашение важнее конфигурации»
- [webpack] Какого черта 7 режимов SourceMap в devtool?