initial commit

This commit is contained in:
Herculino Trotta
2024-09-26 11:00:40 -03:00
parent 830e821a17
commit 50b0c6ce01
138 changed files with 13566 additions and 46 deletions
+15
View File
@@ -0,0 +1,15 @@
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": "3.0.0"
}
]
],
"plugins": [
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-proposal-class-properties"
]
}
+11
View File
@@ -0,0 +1,11 @@
[production staging]
>5%
last 2 versions
not ie > 0
not ie_mob > 0
Firefox ESR
[development]
last 1 chrome version
last 1 firefox version
last 1 edge version
+18
View File
@@ -0,0 +1,18 @@
{
"parser": "@babel/eslint-parser",
"extends": [
"eslint:recommended"
],
"env": {
"browser": true,
"node": true
},
"parserOptions": {
"ecmaVersion": 8,
"sourceType": "module",
"requireConfigFile": false
},
"rules": {
"semi": 2
}
}
+1
View File
@@ -0,0 +1 @@
lts/hydrogen
+15
View File
@@ -0,0 +1,15 @@
{
"extends": "stylelint-config-standard-scss",
"rules": {
"at-rule-no-unknown": null,
"scss/at-rule-no-unknown": [
true,
{
"ignoreAtRules": [
"tailwind"
]
}
],
"scss/at-import-partial-extension": null
}
}
+21
View File
@@ -0,0 +1,21 @@
# README
This project was created with [python-webpack-boilerplate](https://github.com/AccordBox/python-webpack-boilerplate)
## Available Scripts
In the project directory, you can run:
### `npm run start`
`npm run start` will launch a server process, which makes `live reloading` possible.
If you change JS or SCSS files, the web page would auto refresh after the change. Now the server is working on port 9091 by default, but you can change it in `webpack/webpack.config.dev.js`
### `npm run watch`
run webpack in `watch` mode.
### `npm run build`
[production mode](https://webpack.js.org/guides/production/), Webpack would focus on minified bundles, lighter weight source maps, and optimized assets to improve load time.
View File
+9922
View File
File diff suppressed because it is too large Load Diff
+69
View File
@@ -0,0 +1,69 @@
{
"name": "python-webpack-boilerplate",
"version": "1.0.1",
"description": "Webpack boilerplate for Django & Flask",
"scripts": {
"build": "cross-env NODE_ENV=production webpack --config webpack/webpack.config.prod.js",
"start": "webpack serve --config webpack/webpack.config.dev.js",
"watch": "webpack --watch --config webpack/webpack.config.watch.js"
},
"repository": {
"type": "git",
"url": "https://github.com/AccordBox/python-webpack-boilerplate"
},
"keywords": [
"webpack",
"startkit",
"frontend"
],
"author": "Michael Yin",
"license": "MIT",
"bugs": {
"url": "https://github.com/AccordBox/python-webpack-boilerplate/issues"
},
"dependencies": {
"@babel/core": "^7.16.7",
"@babel/eslint-parser": "^7.16.5",
"@babel/plugin-proposal-class-properties": "^7.16.7",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/preset-env": "^7.16.8",
"@fortawesome/fontawesome-free": "^6.6.0",
"@popperjs/core": "^2.11.8",
"alpinejs": "^3.14.1",
"autoprefixer": "^10.4.14",
"autosize": "^6.0.1",
"babel-loader": "^8.2.3",
"bootstrap": "^5.3.3",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^11.0.0",
"core-js": "^3.20.3",
"cross-env": "^7.0.3",
"css-loader": "^6.8.1",
"eslint": "^8.7.0",
"eslint-webpack-plugin": "^3.1.1",
"htmx.org": "^2.0.1",
"hyperscript.org": "^0.9.12",
"jquery": "^3.7.1",
"jquery-mask-plugin": "^1.14.16",
"mini-css-extract-plugin": "^2.5.1",
"postcss-loader": "^6.2.1",
"postcss-preset-env": "^7.2.3",
"sass": "~1.49.9",
"sass-loader": "^13.3.2",
"select2-bootstrap-5-theme": "^1.3.0",
"style-loader": "^3.3.3",
"stylelint": "^14.2.0",
"stylelint-config-standard-scss": "^6.1.0",
"stylelint-webpack-plugin": "^3.1.1",
"sweetalert2": "^11.14.1",
"tailwindcss": "^3.4.1",
"terser-webpack-plugin": "^5.3.10",
"tom-select": "^2.3.1",
"typer-dot-js": "^0.1.0",
"webpack": "^5.66.0",
"webpack-assets-manifest": "^5.1.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.7.3",
"webpack-merge": "^5.8.0"
}
}
+12
View File
@@ -0,0 +1,12 @@
const postcssPresetEnv = require("postcss-preset-env");
const tailwindcss = require('tailwindcss');
module.exports = {
plugins: [postcssPresetEnv({
/* use stage 2 features + disable logical properties and values rule */
stage: 2,
features: {
'logical-properties-and-values': false
}
}), tailwindcss],
};
+54
View File
@@ -0,0 +1,54 @@
import autosize from "autosize/dist/autosize";
let autosize_textareas = document.querySelectorAll('textarea[autosize]');
autosize(autosize_textareas);
document.addEventListener('shown.bs.collapse', function () {
autosize.update(autosize_textareas);
});
// UPDATE AUTOSIZE TEXT AREAS FOR FORMS INSIDE HTMX MODALS
document.addEventListener('updated.bs.modal', function () {
let new_autosize_textareas = document.querySelectorAll('textarea[autosize]');
autosize(new_autosize_textareas);
});
let charcount_textareas = document.querySelectorAll('textarea[countchars], input[countchars]');
charcount_textareas.forEach(formElement => {
countTextArea(formElement);
formElement.addEventListener('input', () => countTextArea(formElement));
});
function countTextArea(formElement) {
let name = formElement.name;
let max_chars = null;
if (formElement.dataset.maxChars) {
max_chars = formElement.dataset.maxChars;
} else if (formElement.hasAttribute("maxlength")) {
max_chars = formElement.getAttribute("maxlength");
}
let cur_chars = formElement.value.length;
let wrapper = document.querySelector(`#charcount-${name}`);
let char_counter = document.querySelector(`#char-counter-${name}`);
let max_counter = document.querySelector(`#max-counter-${name}`);
char_counter.textContent = cur_chars;
if (max_counter) {
max_counter.textContent = max_chars;
wrapper.classList.remove("text-bg-warning", "text-bg-normal", "text-bg-success", "text-bg-danger");
if (cur_chars === 0) {
wrapper.classList.add("text-bg-secondary");
} else if (cur_chars > max_chars - 1) {
wrapper.classList.add("text-bg-danger");
} else if (cur_chars < max_chars && cur_chars > max_chars * (90 / 100)) {
wrapper.classList.add("text-bg-warning");
} else if (cur_chars < max_chars - ((max_chars * (10 / 100)) - 1)) {
wrapper.classList.add("text-bg-success");
}
}
}
+76
View File
@@ -0,0 +1,76 @@
// Import all of Bootstrap's JS
import * as bootstrap from 'bootstrap'; // eslint-disable-line no-unused-vars
window.bootstrap = bootstrap;
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
[...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)); // eslint-disable-line no-undef
function initiateTooltips() {
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
[...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl, {trigger: 'hover', container: 'body'})); // eslint-disable-line no-undef
}
document.addEventListener('updated.bs.modal', initiateTooltips, false);
document.addEventListener('htmx:afterSwap', initiateTooltips, false);
initiateTooltips();
function initiateToasts() {
const toastElList = document.querySelectorAll('.toast');
const toastList = [...toastElList].map(toastEl => new bootstrap.Toast(toastEl)); // eslint-disable-line no-undef
for (let i = 0; i < toastList.length; i++) {
toastList[i].show();
toastList[i]._element.addEventListener('hidden.bs.toast', (event) => {
event.target.remove();
});
}
}
document.addEventListener('htmx:afterSwap', initiateToasts, false);
initiateToasts();
(function ($bs) {
const CLASS_NAME = 'has-child-dropdown-show';
$bs.Dropdown.prototype.toggle = function (_orginal) {
return function () {
document.querySelectorAll('.' + CLASS_NAME).forEach(function (e) {
e.classList.remove(CLASS_NAME);
});
let dd = this._element.closest('.dropdown').parentNode.closest('.dropdown');
for (; dd && dd !== document; dd = dd.parentNode.closest('.dropdown')) {
dd.classList.add(CLASS_NAME);
}
return _orginal.call(this);
};
}($bs.Dropdown.prototype.toggle);
document.querySelectorAll('.dropdown').forEach(function (dd) {
dd.addEventListener('hide.bs.dropdown', function (e) {
if (this.classList.contains(CLASS_NAME)) {
this.classList.remove(CLASS_NAME);
e.preventDefault();
}
e.stopPropagation(); // do not need pop in multi level mode
});
});
// for hover
document.querySelectorAll('.dropdown-hover, .dropdown-hover-all .dropdown').forEach(function (dd) {
dd.addEventListener('mouseenter', function (e) {
let toggle = e.target.querySelector(':scope>[data-bs-toggle="dropdown"]');
if (!toggle.classList.contains('show')) {
$bs.Dropdown.getOrCreateInstance(toggle).toggle();
dd.classList.add(CLASS_NAME);
$bs.Dropdown.clearMenus(e);
}
});
dd.addEventListener('mouseleave', function (e) {
let toggle = e.target.querySelector(':scope>[data-bs-toggle="dropdown"]');
if (toggle.classList.contains('show')) {
$bs.Dropdown.getOrCreateInstance(toggle).toggle();
}
});
});
})(bootstrap);
+5
View File
@@ -0,0 +1,5 @@
// import '@fortawesome/fontawesome-free/js/fontawesome';
// import '@fortawesome/fontawesome-free/js/solid';
// import '@fortawesome/fontawesome-free/js/regular';
// import '@fortawesome/fontawesome-free/js/brands';
// DEPRECATED
+37
View File
@@ -0,0 +1,37 @@
import htmx from "htmx.org";
import _hyperscript from 'hyperscript.org/dist/_hyperscript.min';
_hyperscript.browserInit();
let modalEle = document.getElementById("modal");
if (modalEle) {
const modal = new bootstrap.Modal(modalEle); // eslint-disable-line no-undef
htmx.on("htmx:beforeSwap", (e) => {
// Empty response targeting #dialog => hide the modal
if (e.detail.target.id === "dialog" && !e.detail.xhr.response) {
modal.hide();
e.detail.shouldSwap = false;
}
});
htmx.on("hidden.bs.modal", () => {
document.getElementById("dialog").innerHTML = "";
});
}
let successAudio = new Audio("/static/sounds/success.mp3");
let popAudio = new Audio("/static/sounds/pop.mp3");
htmx.on("paid", () => {
successAudio.pause();
successAudio.currentTime = 0;
successAudio.play();
});
htmx.on("unpaid", () => {
popAudio.pause();
popAudio.currentTime = 0;
popAudio.play();
});
+3
View File
@@ -0,0 +1,3 @@
const $ = require('jquery');
window.jQuery = $;
window.$ = $;
+1
View File
@@ -0,0 +1 @@
import 'jquery-mask-plugin/dist/jquery.mask.min';
+89
View File
@@ -0,0 +1,89 @@
import TomSelect from "tom-select";
import * as Popper from "@popperjs/core";
const multiple_config = {
plugins: {
'checkbox_options': {
'checkedClassNames': ['ts-checked'],
'uncheckedClassNames': ['ts-unchecked'],
},
'clear_button': {
'title': 'Limpar',
},
"remove_button": {
"title": 'Remover',
}
},
render: {
no_results: function () {
return '<div class="no-results">Nenhum resultado encontrado...</div>';
},
},
onInitialize: function () {
//this.popper = Popper.createPopper(this.control,this.dropdown);
this.popper = Popper.createPopper(this.control, this.dropdown, {
placement: "bottom-start",
modifiers: [
{
name: "sameWidth",
enabled: true,
fn: ({state}) => {
state.styles.popper.width = `${state.rects.reference.width}px`;
},
phase: "beforeWrite",
requires: ["computeStyles"],
}
]
});
},
onDropdownOpen: function () {
this.popper.update();
}
};
const single_config = {
allowEmptyOption: false,
// render: {
// no_results: function () {
// return '<div class="no-results">-------</div>';
// },
// },
onInitialize: function () {
this.popper = Popper.createPopper(this.control, this.dropdown, {
placement: "bottom-start",
modifiers: [
{
name: "sameWidth",
enabled: true,
fn: ({state}) => {
state.styles.popper.width = `${state.rects.reference.width}px`;
},
phase: "beforeWrite",
requires: ["computeStyles"],
}
]
});
},
onDropdownOpen: function () {
this.popper.update();
}
};
document.querySelectorAll('.selectmultiple').forEach((el)=>{
new TomSelect(el, multiple_config);
});
document.querySelectorAll('.select').forEach((el)=>{
new TomSelect(el, single_config);
});
document.querySelectorAll('.csvselect').forEach((el)=>{
new TomSelect(el, single_config);
});
+2
View File
@@ -0,0 +1,2 @@
// Import our custom CSS
import '../styles/style.scss';
+2
View File
@@ -0,0 +1,2 @@
import Swal from 'sweetalert2';
window.Swal = Swal;
+101
View File
@@ -0,0 +1,101 @@
@import 'bootstrap/scss/functions';
@import "_variables";
// Import all of Bootstrap's CSS
@import "bootstrap/scss/bootstrap";
.form-label {
font-size: $font-size-base; // Fixes crispy-bootstrap5 legend labels being too big
font-weight: bold;
}
//
////body {
//// background-color: $background-color;
////}
//
//
//// ===== Scrollbar CSS =====
//* {
// scrollbar-width: thin;
// scrollbar-color: $scroll-thumb-color #dfe9eb;
//}
//
//// Chrome, Edge and Safari
//*::-webkit-scrollbar {
// height: 10px;
// width: 10px;
//}
//
//*::-webkit-scrollbar-track {
// border-radius: 5px;
// background-color: $scroll-track-color;
//}
//
//*::-webkit-scrollbar-track:hover {
// background-color: $scroll-thumb-active-color;
//}
//
//*::-webkit-scrollbar-track:active {
// background-color: $scroll-thumb-active-color;
//}
//
//*::-webkit-scrollbar-thumb {
// border-radius: 5px;
// background-color: $scroll-thumb-color;
//}
//
//*::-webkit-scrollbar-thumb:hover {
// background-color: $scroll-thumb-active-color;
//}
//
//*::-webkit-scrollbar-thumb:active {
// background-color: $scroll-thumb-active-color;
//}
//
//// ===== Scrollbar CSS =====
//
//.gradient-background {
// background: linear-gradient(109deg, #652e7c, #1a7976);
// background-size: 120% 120%;
// animation: gradient-animation 8s ease infinite;
//}
//
//@keyframes gradient-animation {
// 0% {
// background-position: 0% 50%;
// }
//
// 50% {
// background-position: 100% 50%;
// }
//
// 100% {
// background-position: 0% 50%;
// }
//}
//
//
//.fade-me-in.htmx-added {
// opacity: 0;
//}
//
//.fade-me-in {
// opacity: 1;
// transition: opacity 500ms ease-out;
//}
.offcanvas-size-xl {
--#{$prefix}offcanvas-width: min(95vw, 600px) !important;
}
.offcanvas-size-xxl {
--#{$prefix}offcanvas-width: min(95vw, 90vw) !important;
}
.offcanvas-size-md { /* add Responsivenes to default offcanvas */
--#{$prefix}offcanvas-width: min(95vw, 400px) !important;
}
.offcanvas-size-sm {
--#{$prefix}offcanvas-width: min(95vw, 250px) !important;
}
+4
View File
@@ -0,0 +1,4 @@
@import "@fortawesome/fontawesome-free/css/fontawesome.min.css";
@import "@fortawesome/fontawesome-free/css/solid.min.css";
@import "@fortawesome/fontawesome-free/css/regular.min.css";
@import "@fortawesome/fontawesome-free/css/brands.min.css";
+3
View File
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+45
View File
@@ -0,0 +1,45 @@
@import "tom-select/dist/css/tom-select.bootstrap5.min.css";
@import "_variables.scss";
/* FIX BOOTSTRAP STYLING FOR TOM SELECT */
.ts-dropdown, .ts-control, .ts-control input {
color: var(--bs-body-color);
}
.ts-wrapper.plugin-remove_button .item .remove {
color: $red;
}
.plugin-clear_button .clear-button {
color: $red;
right: 60px;
}
.ts-wrapper.multi .ts-control > div.active {
background: $primary;
color: $body-color;
border: 0 solid rgba(0, 0, 0, 0);
}
.focus .ts-control {
color: $input-focus-color;
background-color: $input-focus-bg;
border-color: $input-focus-border-color;
//border-color: $form-select-focus-border-color;
outline: 0;
@if $enable-shadows {
@include box-shadow($input-box-shadow, $input-focus-box-shadow);
} @else {
// Avoid using mixin so we can pass custom focus shadow properly
box-shadow: $input-focus-box-shadow;
}
}
.focus.ts-wrapper {
border-color: $form-select-focus-border-color;
outline: 0;
@if $enable-shadows {
@include box-shadow($form-select-box-shadow, $form-select-focus-box-shadow);
} @else {
// Avoid using mixin so we can pass custom focus shadow properly
box-shadow: $form-select-focus-box-shadow;
}
}
+33
View File
@@ -0,0 +1,33 @@
$white: #fff !default;
$gray-100: #f8f9fa !default;
$gray-200: #ebebeb !default;
$gray-300: #dee2e6 !default;
$gray-400: #ced4da !default;
$gray-500: #adb5bd !default;
$gray-600: #888 !default;
$gray-700: #444 !default;
$gray-800: #303030 !default;
$gray-900: #222 !default;
$black: #000 !default;
$blue: #375a7f !default;
$indigo: #6610f2 !default;
$purple: #6f42c1 !default;
$pink: #e83e8c !default;
$red: #e74c3c !default;
$orange: #fd7e14 !default;
$yellow: #fbb700 !default;
$green: #00bc8c !default;
$teal: #20c997 !default;
$cyan: #3498db !default;
$primary: $yellow !default;
$secondary: $gray-700 !default;
$success: $green !default;
$info: $cyan !default;
$warning: $yellow !default;
$danger: $red !default;
//$light: $gray-500 !default;
$dark: $gray-800 !default;
$min-contrast-ratio: 1.9 !default;
+15
View File
@@ -0,0 +1,15 @@
@import "_variables";
@import "_font-awesome.scss";
@import "_tailwind.scss";
@import "_bootstrap.scss";
@import "_tom-select.scss";
.sticky-sidebar {
position: sticky;
top: 10px;
}
// Somewhat prevents flickering when replacing with Tom-select
select[multiple] {
visibility: hidden;
}
+14
View File
@@ -0,0 +1,14 @@
module.exports = {
content: ['../app/templates/**/*.html'],
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
prefix: 'tw-',
corePlugins: {
preflight: false,
},
}
View File
+79
View File
@@ -0,0 +1,79 @@
const glob = require("glob");
const Path = require("path");
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const WebpackAssetsManifest = require("webpack-assets-manifest");
const webpack = require("webpack");
const getEntryObject = () => {
const entries = {};
glob.sync(Path.join(__dirname, "../src/application/*.js")).forEach((path) => {
const name = Path.basename(path, ".js");
entries[name] = path;
});
return entries;
};
module.exports = {
entry: getEntryObject(),
output: {
path: Path.join(__dirname, "../build"),
filename: "js/[name].js",
publicPath: "/static/",
assetModuleFilename: "[path][name][ext]",
},
// optimization: {
// splitChunks: {
// chunks: "all",
// },
//
// runtimeChunk: "single",
// },
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: [
{from: Path.resolve(__dirname, "../vendors"), to: "vendors"},
],
}),
new CopyWebpackPlugin({
patterns: [
{from: Path.resolve(__dirname, "../assets"), to: "assets"},
],
}),
new WebpackAssetsManifest({
entrypoints: true,
output: "manifest.json",
writeToDisk: true,
publicPath: true,
}),
// new webpack.ProvidePlugin({
// $: "jquery",
// jQuery: "jquery",
// })
],
resolve: {
alias: {
"~": Path.resolve(__dirname, "../src"),
jquery: "jquery/dist/jquery"
},
},
module: {
rules: [
{
test: /\.mjs$/,
include: /node_modules/,
type: "javascript/auto",
},
{
test: /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2)(\?.*)?$/,
type: "asset",
},
{
test: /\.css$/i,
include: Path.resolve(__dirname, 'src'),
use: ['style-loader', 'css-loader', 'postcss-loader'],
},
],
},
};
+73
View File
@@ -0,0 +1,73 @@
const Path = require("path");
const Webpack = require("webpack");
const { merge } = require("webpack-merge");
const StylelintPlugin = require("stylelint-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const ESLintPlugin = require("eslint-webpack-plugin");
const common = require("./webpack.common.js");
module.exports = merge(common, {
target: "web",
mode: "development",
devtool: "inline-source-map",
output: {
chunkFilename: "js/[name].chunk.js",
publicPath: "http://localhost:9091/",
},
devServer: {
hot: true,
host: "0.0.0.0",
port: 9091,
headers: {
"Access-Control-Allow-Origin": "*",
},
devMiddleware: {
writeToDisk: true,
},
},
plugins: [
new Webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify("development"),
}),
new StylelintPlugin({
files: Path.resolve(__dirname, "../src/**/*.s?(a|c)ss"),
}),
new ESLintPlugin({
extensions: "js",
emitWarning: true,
files: Path.resolve(__dirname, "../src"),
}),
new MiniCssExtractPlugin({
filename: "css/[name].css",
chunkFilename: "css/[id].css",
}),
],
module: {
rules: [
{
test: /\.html$/i,
loader: "html-loader",
},
{
test: /\.js$/,
include: Path.resolve(__dirname, "../src"),
loader: "babel-loader",
},
{
test: /\.s?css$/i,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
sourceMap: true,
},
},
"postcss-loader",
"sass-loader",
],
},
],
},
});
+45
View File
@@ -0,0 +1,45 @@
const Webpack = require("webpack");
const { merge } = require("webpack-merge");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const common = require("./webpack.common.js");
module.exports = merge(common, {
mode: "production",
bail: true,
output: {
filename: "js/[name].[chunkhash:8].js",
chunkFilename: "js/[name].[chunkhash:8].chunk.js",
},
plugins: [
new Webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify("production"),
}),
new MiniCssExtractPlugin({
filename: "css/[name].[contenthash].css",
chunkFilename: "css/[id].[contenthash].css",
}),
],
optimization: {
minimize: true,
minimizer: [new TerserPlugin()],
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: "babel-loader",
},
{
test: /\.s?css/i,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader",
"sass-loader",
],
},
],
}
});
+61
View File
@@ -0,0 +1,61 @@
const Path = require("path");
const Webpack = require("webpack");
const { merge } = require("webpack-merge");
const StylelintPlugin = require("stylelint-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const ESLintPlugin = require("eslint-webpack-plugin");
const common = require("./webpack.common.js");
module.exports = merge(common, {
target: "web",
mode: "development",
devtool: "inline-source-map",
output: {
chunkFilename: "js/[name].chunk.js",
},
plugins: [
new Webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify("development"),
}),
new StylelintPlugin({
files: Path.resolve(__dirname, "../src/**/*.s?(a|c)ss"),
}),
new ESLintPlugin({
extensions: "js",
emitWarning: true,
files: Path.resolve(__dirname, "../src"),
}),
new MiniCssExtractPlugin({
filename: "css/[name].css",
chunkFilename: "css/[id].css",
}),
],
module: {
rules: [
{
test: /\.html$/i,
loader: "html-loader",
},
{
test: /\.js$/,
include: Path.resolve(__dirname, "../src"),
loader: "babel-loader",
},
{
test: /\.s?css$/i,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
sourceMap: true,
},
},
"postcss-loader",
"sass-loader",
],
},
],
},
});