#3: Setup Tailwind and Flowbite
All checks were successful
Quality Check / QS Backend (push) Successful in 20s
Quality Check / QS Mixed (push) Successful in 35s
Quality Check / QS Frontend (push) Successful in 41s

This commit is contained in:
Snoweuph 2024-07-14 16:26:52 +02:00
parent 651df3b0f1
commit e776683a18
Signed by: Snoweuph
GPG key ID: A494330694B208EF
33 changed files with 2212 additions and 115 deletions

2
.gitignore vendored
View file

@ -1 +1 @@
.idea/* .idea/

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="256" height="256" xmlns="http://www.w3.org/2000/svg">
<defs/>
<path d="M49.87 76.493c-28.61 28.61-28.61 74.992 0 103.598L125.78 256l89.538-90.112c13.89-13.89 19.517-56.259-2.294-80.364l-.666 9.344a72.955 72.955 0 0 1-15.98 40.592l-67.158 67.159a37.34 37.34 0 0 1 0-52.808l25.405-25.405c26.553-26.547 26.553-69.596 0-96.149L126.363 0Zm23.485 70.006a30.029 30.029 0 0 1 0-42.47l53.264-53.254 2.99 2.99a31.928 31.928 0 0 1 0 45.153l-51.917 51.917z" style="stroke-width: 5.12; fill: white;"/>
</svg>

After

Width:  |  Height:  |  Size: 551 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256"><path d="M49.87 76.493c-28.61 28.61-28.61 74.992 0 103.598L125.78 256l89.538-90.112c13.89-13.89 19.517-56.259-2.294-80.364l-.666 9.344a72.955 72.955 0 0 1-15.98 40.592l-67.158 67.159a37.34 37.34 0 0 1 0-52.808l25.405-25.405c26.553-26.547 26.553-69.596 0-96.149L126.363 0Zm23.485 70.006a30.029 30.029 0 0 1 0-42.47l53.264-53.254 2.99 2.99a31.928 31.928 0 0 1 0 45.153l-51.917 51.917z" style="stroke-width:5.12"/></svg>

After

Width:  |  Height:  |  Size: 482 B

View file

@ -48,7 +48,7 @@ install:
# Starts the local Development Setup # Starts the local Development Setup
[group('main')] [group('main')]
up: up: halt
-@cd .. && docker compose up -d -@cd .. && docker compose up -d
-@cd .. && symfony local:server:start -d --no-tls -@cd .. && symfony local:server:start -d --no-tls

View file

@ -13,6 +13,7 @@
"doctrine/doctrine-migrations-bundle": "^3.3", "doctrine/doctrine-migrations-bundle": "^3.3",
"doctrine/orm": "^2.17", "doctrine/orm": "^2.17",
"league/commonmark": "^2.4", "league/commonmark": "^2.4",
"nelmio/cors-bundle": "^2.5",
"presta/sitemap-bundle": "^4.1", "presta/sitemap-bundle": "^4.1",
"symfony/apache-pack": "^1.0", "symfony/apache-pack": "^1.0",
"symfony/asset": "7.1.*", "symfony/asset": "7.1.*",

64
app/composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "69c16513a892fb6b25ff5563230dabc1", "content-hash": "ee85551bd6012966efcff3c2d44a4b98",
"packages": [ "packages": [
{ {
"name": "dflydev/dot-access-data", "name": "dflydev/dot-access-data",
@ -1760,6 +1760,68 @@
], ],
"time": "2024-06-28T09:40:51+00:00" "time": "2024-06-28T09:40:51+00:00"
}, },
{
"name": "nelmio/cors-bundle",
"version": "2.5.0",
"source": {
"type": "git",
"url": "https://github.com/nelmio/NelmioCorsBundle.git",
"reference": "3a526fe025cd20e04a6a11370cf5ab28dbb5a544"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/3a526fe025cd20e04a6a11370cf5ab28dbb5a544",
"reference": "3a526fe025cd20e04a6a11370cf5ab28dbb5a544",
"shasum": ""
},
"require": {
"psr/log": "^1.0 || ^2.0 || ^3.0",
"symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0"
},
"require-dev": {
"mockery/mockery": "^1.3.6",
"symfony/phpunit-bridge": "^5.4 || ^6.0 || ^7.0"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
},
"autoload": {
"psr-4": {
"Nelmio\\CorsBundle\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nelmio",
"homepage": "http://nelm.io"
},
{
"name": "Symfony Community",
"homepage": "https://github.com/nelmio/NelmioCorsBundle/contributors"
}
],
"description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Symfony application",
"keywords": [
"api",
"cors",
"crossdomain"
],
"support": {
"issues": "https://github.com/nelmio/NelmioCorsBundle/issues",
"source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.5.0"
},
"time": "2024-06-24T21:25:28+00:00"
},
{ {
"name": "nette/schema", "name": "nette/schema",
"version": "v1.3.0", "version": "v1.3.0",

View file

@ -15,4 +15,5 @@ return [
Vich\UploaderBundle\VichUploaderBundle::class => ['all' => true], Vich\UploaderBundle\VichUploaderBundle::class => ['all' => true],
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
Presta\SitemapBundle\PrestaSitemapBundle::class => ['all' => true], Presta\SitemapBundle\PrestaSitemapBundle::class => ['all' => true],
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
]; ];

View file

@ -0,0 +1,14 @@
nelmio_cors:
defaults:
allow_credentials: false
allow_origin: []
# Allow Hot Reloading CORS
when@dev:
nelmio_cors:
defaults:
origin_regex: false
allow_origin: [ 'http://localhost:3000' ]
allow_headers: ['Content-Type', "X-Requested-With"]
allow_methods: [ 'POST', 'PUT', 'GET', 'DELETE' ]
skip_same_as_origin: true

View file

@ -6,3 +6,10 @@ controllers:
presta_sitemap: presta_sitemap:
resource: "@PrestaSitemapBundle/config/routing.yml" resource: "@PrestaSitemapBundle/config/routing.yml"
when@dev:
test_style:
path: /test/styles
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
defaults:
template: 'test/styles.html.twig'

View file

@ -1,2 +1,3 @@
import '@styles/app.scss'; import '@styles/app.scss';
import '@packages/stimulus'; import '@pkg/stimulus';
import '@pkg/flowbite';

View file

@ -0,0 +1,5 @@
declare enum HttpCodes {
OK = 200
}
export {HttpCodes};

View file

@ -0,0 +1,8 @@
const THEME_LOCAL_STORAGE_ID = 'theme';
enum Theme {
LIGHT = 'light',
DARK = 'dark'
}
export {THEME_LOCAL_STORAGE_ID, Theme};

View file

@ -0,0 +1,40 @@
import {Theme, THEME_LOCAL_STORAGE_ID} from '@const/theme';
const DARK_MODE_QUERY: MediaQueryList = window.matchMedia('(prefers-color-scheme: dark)');
function initTheme(): void {
handleTheme(DARK_MODE_QUERY.matches);
DARK_MODE_QUERY.addEventListener('change', handleThemeChange);
}
function handleThemeChange(e: MediaQueryListEvent): void {
handleTheme(e.matches);
}
function handleTheme(prefersDark: boolean): void {
const theme = localStorage[THEME_LOCAL_STORAGE_ID];
if (theme === Theme.DARK) {
document.documentElement.classList.add(Theme.DARK);
console.log('explicit dark');
} else if (!(THEME_LOCAL_STORAGE_ID in localStorage) && prefersDark) {
document.documentElement.classList.add(Theme.DARK);
console.log('implicit dark');
} else {
document.documentElement.classList.remove(Theme.DARK);
console.log('light');
}
}
export {initTheme};
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
// Whenever the user explicitly chooses light mode
//localStorage.theme = 'light';
// Whenever the user explicitly chooses dark mode
//localStorage.theme = 'dark';
// Whenever the user explicitly chooses to respect the OS preference
//localStorage.removeItem('theme');

View file

@ -0,0 +1,4 @@
import 'flowbite';
import {initFlowbite} from 'flowbite';
initFlowbite();

View file

@ -1,4 +1,4 @@
import { startStimulusApp } from '@symfony/stimulus-bridge'; import {startStimulusApp} from '@symfony/stimulus-bridge';
// Registers Stimulus controllers from controllers.json and in the controllers/ directory // Registers Stimulus controllers from controllers.json and in the controllers/ directory
export const app = startStimulusApp( export const app = startStimulusApp(

View file

@ -1,5 +1,8 @@
{ {
"extends": "stylelint-config-standard-scss", "extends": [
"stylelint-config-standard-scss",
"stylelint-config-tailwindcss/scss"
],
"plugins": [ "plugins": [
"stylelint-scss" "stylelint-scss"
], ],

View file

@ -1 +1,3 @@
// This is the Main style file @tailwind base;
@tailwind components;
@tailwind utilities;

View file

@ -3,28 +3,24 @@
<head> <head>
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="description" content="{% block description %}{% endblock %}" <meta name="description" content="{% block description %}{% endblock %}"/>
/>
<link rel="icon" type="image/svg+xml" href="{{ asset('icons/favicon/icon.svg') }}"/> <link rel="icon" type="image/svg+xml" href="{{ asset('icons/favicon/icon.svg') }}"/>
<link rel="apple-touch-icon" sizes="180x180" href="{{ asset('/apple-touch-icon.png') }}"> <link rel="apple-touch-icon" sizes="180x180" href="{{ asset('icons/favicon/apple-touch-icon.png') }}">
<link rel="icon" type="image/png" sizes="32x32" href="{{ asset('/favicon-32x32.png') }}"> <link rel="icon" type="image/png" sizes="32x32" href="{{ asset('icons/favicon/favicon-32x32.png') }}">
<link rel="icon" type="image/png" sizes="16x16" href="{{ asset('/favicon-16x16.png') }}"> <link rel="icon" type="image/png" sizes="16x16" href="{{ asset('icons/favicon/favicon-16x16.png') }}">
<link rel="manifest" href="{{ asset('/site.webmanifest') }}"> <link rel="manifest" href="{{ asset('/site.webmanifest') }}">
<link rel="mask-icon" href="{{ asset('/safari-pinned-tab.svg') }}" color="#000000"> <link rel="mask-icon" href="{{ asset('/safari-pinned-tab.svg') }}" color="#000000">
<meta name="msapplication-TileColor" content="#000000"> <meta name="msapplication-TileColor" content="#000000">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#ffffff">
{{ encore_entry_link_tags('app') }}
{{ encore_entry_script_tags('theme') }}
{{ encore_entry_script_tags('app') }}
<title>{% block title %}Euph{% endblock %}</title> <title>{% block title %}Euph{% endblock %}</title>
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
{% endblock %}
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
</head> </head>
<body> <body class="bg-gray-100 dark:bg-gray-600">
{% include 'components/header.html.twig' %}
{% block body %}{% endblock %} {% block body %}{% endblock %}
</body> </body>
</html> </html>

View file

@ -0,0 +1,51 @@
<header class="flex h-12 p-2 gap-2 bg-gray-300 dark:bg-gray-700 items-center">
<img src="{{ asset("icons/logo/logo.svg") }}" alt="Euph Logo" class="dark:hidden h-full">
<img src="{{ asset("icons/logo/dark-logo.svg") }}" alt="Euph Logo" class="hidden dark:block h-full">
<h1 class="text-lg text-center text-black dark:text-white">Euph</h1>
<button
type="button"
data-drawer-target="drawer-navigation"
data-drawer-show="drawer-navigation"
data-drawer-placement="right"
class="ml-auto h-full aspect-square"
aria-controls="drawer-navigation"
>
{% include 'icons/bars.svg.twig' with {'class': 'h-full w-full'} %}
</button>
<!-- drawer component -->
<div
id="drawer-navigation"
class="fixed top-0 right-0 h-screen z-40 w-80 flex flex-col p-2 gap-2 overflow-y-auto bg-gray-100 dark:bg-gray-800 transition-transform translate-x-full"
tabindex="-1"
>
<button
type="button"
data-drawer-hide="drawer-navigation"
class="h-8 w-8 absolute right-2 aspect-square"
aria-controls="drawer-navigation"
>
{% include 'icons/close.svg.twig' with {'class': 'h-full w-full'} %}
</button>
<nav>
<ul class="flex flex-col gap-2 text-lg text-black dark:text-white">
{{ _self.nav_item('Test1', '#test1', 'bars') }}
{{ _self.nav_item('Test2', '#test2', 'bars') }}
{{ _self.nav_item('Test3', '#test3', 'bars') }}
</ul>
</nav>
</div>
</header>
{% macro nav_item(name, href, icon) %}
<li class="h-8">
<a
href="{{ href }}"
class="flex p-1 gap-2 items-center rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700"
>
{% include 'icons/' ~ icon ~ '.svg.twig' with {'class': 'w-6 h-6'} %}
<span class="flex-1">{{ name }}</span>
</a>
</li>
{% endmacro %}

View file

@ -0,0 +1,4 @@
<svg class="text-gray-800 dark:text-white {{ class|default('') }}" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
width="24" height="24" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="M5 7h14M5 12h14M5 17h14"/>
</svg>

After

Width:  |  Height:  |  Size: 291 B

View file

@ -0,0 +1,5 @@
<svg class="text-gray-800 dark:text-white {{ class|default('') }}" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
width="24" height="24" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M6 18 17.94 6M18 18 6.06 6"/>
</svg>

After

Width:  |  Height:  |  Size: 328 B

View file

@ -0,0 +1,53 @@
{% extends 'base.html.twig' %}
{% block body %}
<h1>H1 Hello World</h1>
<h2>H2 Hello World</h2>
<h3>H3 Hello World</h3>
<h4>H4 Hello World</h4>
<h5>H5 Hello World</h5>
<h6>H6 Hello World</h6>
<p>P Lorem ipsum dolor sit amet, consectetur adipisicing elit.
Ad consectetur consequuntur culpa debitis dignissimos
dolore est ex illo iure maxime, molestiae pariatur recusandae,
reiciendis saepe similique temporibus voluptatum? Eos,
repudiandae!
</p>
<a href="">A Link</a>
<button>Button</button>
<input type="text">
<input type="checkbox" name="" id="">
<button id="dropdownButton" data-dropdown-toggle="dropdownMenu" class="shadow" type="button">
Select an option
</button>
<ul id="dropdownMenu" class="hidden border-red-300 shadow">
<li>
<a href="#" class="block hover:bg-red-500" data-value="1">Option 1</a>
</li>
<li>
<a href="#" class="block hover:bg-green-500" data-value="2">Option 2</a>
</li>
<li>
<a href="#" class="block hover:bg-blue-500" data-value="3">Option 3</a>
</li>
</ul>
<script>
document.querySelectorAll('#dropdownMenu a').forEach(function (option) {
option.addEventListener('click', function (event) {
event.preventDefault();
console.log(this.dataset.value);
document.getElementById('dropdownButton').textContent = this.textContent;
console.log('close');
document.getElementById('dropdownMenu').classList.add('hidden');
});
});
document.getElementById('dropdownButton').addEventListener('click', function () {
let menu = document.getElementById('dropdownMenu');
console.log('open', menu);
menu.classList.remove('hidden');
console.log('open2', menu);
});
</script>
{% endblock %}

3
app/frontend/theme.ts Normal file
View file

@ -0,0 +1,3 @@
import {initTheme} from '@domain/theme';
initTheme();

View file

@ -3,7 +3,7 @@
"target": "ES6", "target": "ES6",
"module": "ESNext", "module": "ESNext",
"moduleResolution": "node", "moduleResolution": "node",
"allowJs": true, "allowJs": false,
"noImplicitAny": true, "noImplicitAny": true,
"strict": true, "strict": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
@ -16,18 +16,24 @@
"@styles/*": [ "@styles/*": [
"./styles/*" "./styles/*"
], ],
"@controllers*": [ "@controllers/*": [
"./controllers/*" "./controllers/*"
], ],
"@packages/*": [ "@pkg/*": [
"./packages/*" "./packages/*"
], ],
"@domain/*": [
"./domain/*"
],
"@const/*": [
"./constants/*"
],
"#types/*": [ "#types/*": [
"./types/*" "./types/*"
] ]
} }
}, },
"include": [ "include": [
"./**/*.ts", "./**/*.ts"
], ]
} }

View file

View file

@ -1,4 +0,0 @@
export enum HttpCodes
{
OK = 200
}

1932
app/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -12,7 +12,8 @@
"lint:scss:fix": "cd frontend/styles && stylelint . --fix" "lint:scss:fix": "cd frontend/styles && stylelint . --fix"
}, },
"dependencies": { "dependencies": {
"easymde": "^2.18.0" "easymde": "^2.18.0",
"flowbite": "^2.4.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.23.3", "@babel/core": "^7.23.3",
@ -24,7 +25,9 @@
"@symfony/webpack-encore": "^4.0.0", "@symfony/webpack-encore": "^4.0.0",
"@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.10.0", "@typescript-eslint/parser": "^6.10.0",
"bootstrap": "^5.3.2", "autoprefixer": "^10.4.19",
"browser-sync": "^2.29.3",
"browser-sync-webpack-plugin": "^2.3.0",
"core-js": "^3.23.0", "core-js": "^3.23.0",
"eslint": "^8.53.0", "eslint": "^8.53.0",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
@ -32,13 +35,17 @@
"ignore-loader": "^0.1.2", "ignore-loader": "^0.1.2",
"jquery": "^3.7.1", "jquery": "^3.7.1",
"junit-report-merger": "^6.0.3", "junit-report-merger": "^6.0.3",
"postcss": "^8.4.39",
"postcss-loader": "^7.3.4",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"regenerator-runtime": "^0.13.9", "regenerator-runtime": "^0.13.9",
"sass": "^1.69.4", "sass": "^1.69.4",
"sass-loader": "^13.3.2", "sass-loader": "^13.3.2",
"stylelint": "^16.2.1", "stylelint": "^16.2.1",
"stylelint-config-standard-scss": "^13.0.0", "stylelint-config-standard-scss": "^13.0.0",
"stylelint-config-tailwindcss": "^0.0.7",
"stylelint-scss": "^6.3.1", "stylelint-scss": "^6.3.1",
"tailwindcss": "^3.4.4",
"ts-loader": "^9.5.0", "ts-loader": "^9.5.0",
"tsconfig-paths-webpack-plugin": "^4.1.0", "tsconfig-paths-webpack-plugin": "^4.1.0",
"typescript": "^5.2.2", "typescript": "^5.2.2",

6
app/postcss.config.js Normal file
View file

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View file

@ -62,6 +62,18 @@
".php-cs-fixer.dist.php" ".php-cs-fixer.dist.php"
] ]
}, },
"nelmio/cors-bundle": {
"version": "2.5",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.5",
"ref": "6bea22e6c564fba3a1391615cada1437d0bde39c"
},
"files": [
"config/packages/nelmio_cors.yaml"
]
},
"phpstan/phpstan": { "phpstan/phpstan": {
"version": "1.10", "version": "1.10",
"recipe": { "recipe": {

18
app/tailwind.config.js Normal file
View file

@ -0,0 +1,18 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./frontend/**/*.ts",
"./frontend/templates/**/*.html.twig",
"./node_modules/flowbite/**/*.js"
],
theme: {
extend: {
},
},
plugins: [
require('flowbite/plugin')
],
darkMode: 'selector'
}

View file

@ -1,7 +1,7 @@
const Encore = require('@symfony/webpack-encore'); const Encore = require('@symfony/webpack-encore');
const {TsconfigPathsPlugin} = require("tsconfig-paths-webpack-plugin"); const {TsconfigPathsPlugin} = require("tsconfig-paths-webpack-plugin");
const path = require('path'); const path = require('path');
const BrowserSyncPlugin = require('browser-sync-webpack-plugin');
if (!Encore.isRuntimeEnvironmentConfigured()) { if (!Encore.isRuntimeEnvironmentConfigured()) {
Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev'); Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
@ -16,11 +16,11 @@ Encore
from: './assets/', from: './assets/',
to: '../[path][name].[ext]', to: '../[path][name].[ext]',
pattern: /\.(?!scss|stylelint.*|json|woff).*$/ pattern: /\.(?!scss|stylelint.*|json|woff).*$/
}) })
// Code Entries // Code Entries
.addEntry('app', './frontend/app.ts') .addEntry('app', './frontend/app.ts')
.addEntry('theme', './frontend/theme.ts')
// Style Entries // Style Entries
//.addStyleEntry('name', './frontend/styles/name.scss') //.addStyleEntry('name', './frontend/styles/name.scss')
@ -40,6 +40,7 @@ Encore
}) })
.enableStimulusBridge('./frontend/controllers.json') .enableStimulusBridge('./frontend/controllers.json')
.enableSassLoader() .enableSassLoader()
.enablePostCssLoader()
.enableTypeScriptLoader(function tsconfigCallback(tsConfig) { .enableTypeScriptLoader(function tsconfigCallback(tsConfig) {
tsConfig.configFile = TS_CONFIG_PATH; tsConfig.configFile = TS_CONFIG_PATH;
}) })
@ -48,6 +49,17 @@ Encore
configFile: TS_CONFIG_PATH configFile: TS_CONFIG_PATH
} }
}) })
.addPlugin(new BrowserSyncPlugin({
proxy: 'http://localhost:8000',
files: [
'frontend/**/*.twig',
'frontend/**/*.scss',
'frontend/**/*.ts',
],
reload: true,
open: true,
notify: false
}))
; ;
const config = Encore.getWebpackConfig(); const config = Encore.getWebpackConfig();
@ -58,5 +70,3 @@ config.resolve.plugins = [
]; ];
module.exports = config; module.exports = config;