Создайте vue2, фреймворк для многостраничных приложений vuex на основе webpack4.

Vue.js Vuex Webpack

задний план

В последнее время компания, выполняющая реконструкцию проекта 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, укажите режим упаковки, необязательные значения:

  1. разработка, режим разработки
    • Настройте process.env.node_env для разработки
    • Включить плагины NamedChunksPlugin, NamedModulesPlugin
  2. производство, режим производства
    • установит process.env.NODE_ENV в производство
    • Включены максимальные оптимизации (сжатие модулей, конкатенация и т. д.)
  3. нет, этот режим не будет оптимизирован

Есть два способа установить режим:

  • Задайте в виде параметров команды оболочки в 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, если вам это нужно, примите это напрямую. Если это поможет вам, пожалуйста, дайте ему звезду ~~

использованная литература