Дисклеймер
Я недавно начал разбираться с Webpack, поэтому некоторые вещи могут показаться странными. Если вы нашли ошибку, или ugly-код, то напишите об этом в комментариях, я буду благодарен.
Описание задачи
Я работаю над небольшим дополнением для сайта. Дополнение включает в себя 2 практически независимые части — личный кабинет
и общедоступную часть
. По сути это два независимых SPA.
После базовой настройки webpack встал вопрос, на который я попробую ответить в этой заметке:
Как настроить webpack 4 чтобы одновременно собирать 2 страницы так, чтобы для каждой подгружались только необходимые бандлы?
Потребности такие:
-
Личный кабинет — back
- vue, vue-router, vuex
- axios
- @riophae/vue-treeselect
- buefy
- Собственные скрипты
-
Общедоступная часть — front
- vue, vue-router, vuex
- axios
- @riophae/vue-treeselect
- Собственные скрипты
Конфиги с решением
И комментариями
const path = require('path'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); //const CopyWebpackPlugin = require('copy-webpack-plugin'); const { VueLoaderPlugin } = require('vue-loader'); // Общие переменные для нескольких конфигов const PATHS = { src_back: path.join(__dirname, '../src-back'), src_front: path.join(__dirname, '../src-front'), // Чанки для back. Вызываются в HtmlWebpackPlugin (в build и dev) back_chunks : ['common_vendors', 'buefy', 'back' ], front_chunks: ['common_vendors', 'front' ], dist: path.join(__dirname, '../docs'), assets: 'assets/', } module.exports = { // точки входа entry: { back: PATHS.src_back, front: PATHS.src_front, }, // точки выхода output: { // Квадратные скобки означают, что берется файл с имеем точки входа https://youtu.be/JcKRovPhGo8?t=916 filename: `js/[name].js?v=[hash]`, // папка назначения скомпилированных файлов https://nodejs.org/api/path.html#path_path_relative_from_to path: PATHS.dist, // Папка, которая отображается, может отличаться от реальной папки publicPath: '/' }, // Разбиваем на отдельные файлы optimization: { splitChunks: { cacheGroups: { common_vendors: { test: /[\\/]node_modules[\\/](vue|vue-router|vuex|axios|@riophae[\\/]vue-treeselect)[\\/]/, name: 'common_vendors', // имя чанка chunks: 'initial', enforce: true, }, buefy: { test: /[\\/]node_modules[\\/](buefy)[\\/]/, name: 'buefy', // имя чанка chunks: 'initial', enforce: true, }, } } }, resolve: { // Порядок обработки файлов. extensions: ['.js', '.vue', '.json'], }, module: { // Определяем порядок обработки разных типов файлов. // Постпроцессоры, минификаторы и пр. rules: [{ test: /\.js$/, loader: "babel-loader", // не включаем те файлы, которые содержет эта папка exclude: "/node_modules/", }, { test: /\.vue$/, loader: "vue-loader", options: { loader: { // Определяем порядок обработки scss: 'vue-style-loader!css-loader!sass-loader' } } }, { test: /\.(png|jpg|jpeg|gif|svg)$/, loader: "file-loader", options: { name: '[name].[ext]' } }, { test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader", options: { name: '[name].[ext]' } }, { test: /\.scss$/, use: [ 'style-loader', MiniCssExtractPlugin.loader, { loader: "css-loader", options: { sourceMap: true } }, { loader: "postcss-loader", options: { sourceMap: true, config: { path: `./js/postcss.config.js` } } }, { loader: "sass-loader", options: { sourceMap: true } }, ] }, { test: /\.less$/, use: [ 'style-loader', MiniCssExtractPlugin.loader, { loader: "css-loader", options: { sourceMap: true } }, { loader: "postcss-loader", options: { sourceMap: true, config: { path: `./js/postcss.config.js` } } }, { loader: "less-loader", options: { sourceMap: true } }, ] }, { test: /\.css$/, use: [ "style-loader", MiniCssExtractPlugin.loader, { loader: "css-loader", options: { sourceMap: true } }, { loader: "postcss-loader", options: { sourceMap: true, config: { path: `./js/postcss.config.js` } } }, ] } ] }, plugins: [ new VueLoaderPlugin(), new MiniCssExtractPlugin({ filename: `css/[name].css?h=[hash]`, }), //new CopyWebpackPlugin([]), ], node: { // Иначе как-то много кода лишнего добавляется. ХЗ:\ Buffer: false }, // Чтобы переменные были доступны в других файлах конфигураций (dev, build) externals: { paths: PATHS }, }
const merge = require('webpack-merge'); const baseWebpackConfig = require('./webpack.base.conf'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const buildWebpackConfig = merge(baseWebpackConfig, { mode: 'production', resolve: { alias: { // Для build используем минифицированный файл библиотеки 'vue$': 'vue/dist/vue.min.js', } }, plugins: [ // Обработка шаблона точки входа "back" new HtmlWebpackPlugin({ template: `${baseWebpackConfig.externals.paths.src_back}/index.html`, filename: `${baseWebpackConfig.externals.paths.dist}/mx_static/reception_points-vueapp-backend.php`, title: "mode_build", // Нужно для костыльного условия в шаблоне inject: false, chunks: baseWebpackConfig.externals.paths.back_chunks, }), // Обработка шаблона точки входа "front" new HtmlWebpackPlugin({ template: `${baseWebpackConfig.externals.paths.src_front}/index.html`, filename: `${baseWebpackConfig.externals.paths.dist}/mx_static/reception_points-vueapp-front.php`, title: "mode_build", // Нужно для костыльного условия в шаблоне inject: false, chunks: baseWebpackConfig.externals.paths.front_chunks, }), ] }); module.exports = new Promise((resolve, reject) => { resolve(buildWebpackConfig); });
const webpack = require('webpack'); const merge = require('webpack-merge'); const baseWebpackConfig = require('./webpack.base.conf'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const devWebpackConfig = merge(baseWebpackConfig, { mode: 'development', devtool: 'cheap-module-eval-source-map', resolve: { alias: { // Для dev используем не минифицированный файл библиотеки 'vue$': 'vue/dist/vue.js', } }, devServer: { contentBase: baseWebpackConfig.externals.paths.dist, // типа localhost (локальный домен настроен на Open Server) host: 'mysite.test', port: 8081, // Выводить ошибки компиляции в браузер overlay:{ warnings: true, errors: true, }, proxy: { // Для проксирования AJAX запросов, иначе будет ошибка политики доступа // С этой настройкой все запросы, содержащие '/api' будут переводиться с // 'http://mysite.test:8081 на http://mysite.test '/api': { target: 'http://mysite.test', } }, }, plugins:[ new webpack.SourceMapDevToolPlugin({ filename: '[file].map' }), // Обработка шаблона точки входа "back" new HtmlWebpackPlugin({ template: `${baseWebpackConfig.externals.paths.src_back}/index.html`, filename: './index.html', title: "mode_development", // Нужно для костыльного условия в шаблоне inject: false, chunks: baseWebpackConfig.externals.paths.back_chunks, }), // Обработка шаблона точки входа "front" new HtmlWebpackPlugin({ template: `${baseWebpackConfig.externals.paths.src_front}/index.html`, filename: './pp.html', title: "mode_development", // Нужно для костыльного условия в шаблоне inject: false, chunks: baseWebpackConfig.externals.paths.front_chunks, }), ] }); module.exports = new Promise((resolve, reject) => { resolve(devWebpackConfig); });
module.exports = { plugins: [ require('autoprefixer'), require('css-mqpacker'), require('cssnano')({ preset: [ 'default', { discardComments: { removeAll: false, }, //normalizeUrl: false } ] }) ] }
<% if (htmlWebpackPlugin.options.title == 'mode_development') { %> <!-- Условие if (htmlWebpackPlugin.options.title == 'mode_development') { Я использую для того, чтобы отобразить все эти элементы при dev-режиме, но скрыть при build, т.к. на проекте мне нужен только элемент <div class="body_content">...</div> --> <!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title><%= htmlWebpackPlugin.options.title || 'Empty title' %></title> </head> <body> <% } %> <div class="body_content"> <!-- Подгружаем CSS Файлы --> <% for (var css in htmlWebpackPlugin.files.css) { %> <link rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[css] %>"> <% } %> <div id="app"> <buefy-navbar></buefy-navbar> <router-view></router-view> </div> <!-- Подгружаем JS Файлы --> <% for (var js in htmlWebpackPlugin.files.js) { %> <script src="<%= htmlWebpackPlugin.files.js[js] %>"></script> <% } %> <script src="https://use.fontawesome.com/a304bad23c.js"></script> </div> <% if (htmlWebpackPlugin.options.title == 'mode_development') { %> </body> </html> <% } %>
{ "name": "server-test", "version": "1.0.0", "description": "test-descr", "main": "index.js", "devDependencies": { "@babel/core": "^7.6.0", "@babel/preset-env": "^7.6.0", "autoprefixer": "^9.6.1", "babel-loader": "^8.0.6", "copy-webpack-plugin": "^5.0.4", "css-loader": "^3.2.0", "css-mqpacker": "^7.0.0", "cssnano": "^4.1.10", "fibers": "^4.0.1", "file-loader": "^4.2.0", "fs": "0.0.1-security", "html-webpack-plugin": "^3.2.0", "mini-css-extract-plugin": "^0.8.0", "node-sass": "^4.12.0", "npm-git-install": "^0.3.0", "path": "^0.12.7", "postcss-loader": "^3.0.0", "pug": "^2.0.4", "pug-loader": "^2.4.0", "sass": "^1.22.12", "sass-loader": "^8.0.0", "style-loader": "^1.0.0", "vue-loader": "^15.7.1", "vue-style-loader": "^4.1.2", "vue-template-compiler": "^2.6.10", "webpack": "^4.40.2", "webpack-cli": "^3.3.9", "webpack-dev-server": "^3.8.1", "webpack-merge": "^4.2.2" }, "scripts": { "dev": "webpack-dev-server --open --config ./build/webpack.dev.conf.js", "build": "webpack --config ./build/webpack.build.conf.js" }, "browserslist": [ "> 1%", "last 3 version" ], "author": "serg_x", "license": "MIT", "dependencies": { "@fortawesome/fontawesome-svg-core": "^1.2.25", "@fortawesome/free-solid-svg-icons": "^5.11.2", "@fortawesome/vue-fontawesome": "^0.1.7", "@riophae/vue-treeselect": "^0.4.0", "axios": "^0.19.0", "buefy": "^0.8.4", "vue": "^2.6.10", "vue-router": "^3.1.3", "vuex": "^3.1.1" } }
Описание решения
В этом описании я буду оперировать тем кодом, который представлен выше в конфигах.
В разделе Конфиги с решением вы найдете полный код.
const PATHS = { src_back: path.join(__dirname, '../src-back'), src_front: path.join(__dirname, '../src-front'), // Чанки для back. Вызываются в HtmlWebpackPlugin (в build и dev) back_chunks : ['common_vendors', 'buefy', 'back' ], front_chunks: ['common_vendors', 'front' ], dist: path.join(__dirname, '../docs'), assets: 'assets/', }
Указываем две точки входа entry
, и сделать динамическим имя для исхзодящих файлов output
// взято из webpack.base.conf.js // Константа PATHS есть в спойлере в этой статье entry: { back: PATHS.src_back, front: PATHS.src_front, }, output: { filename: `js/[name].js?v=[hash]`, path: PATHS.dist, publicPath: '/' },
Вычленяем из output
код для чанков common_vendors
и buefy
- В
common_vendors
записываем то, что нужно везде. - В
buefy
попадают файлы библиотеки Buefy, которая нужная только вback
.
Параметр test
— Это регулярное выражение, которому должен соответствовать буть к файлу
optimization: { splitChunks: { cacheGroups: { common_vendors: { test: /[\\/]node_modules[\\/](vue|vue-router|vuex|axios|@riophae[\\/]vue-treeselect)[\\/]/, name: 'common_vendors', // имя чанка chunks: 'initial', enforce: true, }, buefy: { test: /[\\/]node_modules[\\/](buefy)[\\/]/, name: 'buefy', chunks: 'initial', enforce: true, }, } } },
Добавить обработку двух шаблонов
Ключевой момент — настройка chunks
. Тут мы определяем, какие чанки подгружаем в шаблон.
common_vendors
и buefy
мы определили на предыдущем этапе, а чанки back
и front
формируются какбы автоматически по имени точки входа, указанной в параметре entry
// взято из webpack.dev.conf.js и webpack.build.conf.js // baseWebpackConfig (PATHS) есть в спойлере в этой статье new HtmlWebpackPlugin({ template: `${baseWebpackConfig.externals.paths.src_back}/index.html`, filename: './index.html', title: "mode_development", // Нужно для костыльного условия в шаблоне inject: false, chunks: baseWebpackConfig.externals.paths.back_chunks, // ['common_vendors', 'buefy', 'back' ] }), // Обработка шаблона точки входа "front" new HtmlWebpackPlugin({ template: `${baseWebpackConfig.externals.paths.src_front}/index.html`, filename: './pp.html', title: "mode_development", inject: false, chunks: baseWebpackConfig.externals.paths.front_chunks, // ['common_vendors', 'front' ] }),
Инъекция файлов в HTML (pug/шмаг) шаблон
В предыдущем пункте мы указали настройку inject: false
, поэтому должны сами подключить скрипты в шаблон.
В принципе, это можно сделать и вручную, но давайте не будем так делать:)
<!-- Так не делаем --> <script src="/js/common_vendors.js"></script> <script src="/js/buefy.js"></script> <script src="/js/back.js"></script>
В шаблоне мы можем вызвать конструкцию для подключения JS и CSS:
<!-- Подгружаем CSS Файлы --> <% for (var css in htmlWebpackPlugin.files.css) { %> <link rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[css] %>"> <% } %> <div id="app"> ... </div> <!-- Подгружаем JS Файлы --> <% for (var js in htmlWebpackPlugin.files.js) { %> <script src="<%= htmlWebpackPlugin.files.js[js] %>"></script> <% } %>
На этом все
Пожалуйста, напишите в комментариях, получилось ли у вас все настроить, и была ли понятна и корректна та информация, которую вы нашли в этой статье.
Комментарии (4)
Не писать ответ