diff --git a/.gitignore b/.gitignore index 650aa90..9a7743d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ /.idea -/conf/app.ini \ No newline at end of file +/conf/app.ini +/node_modules +/public/assets/css/* +/public/assets/js/* +package-lock.json \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..0c6e5cf --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "scripts": { + "build": "webpack --config webpack.config.js --mode=production", + "dev": "webpack --config webpack.config.js --mode=development", + "watch": "webpack --config webpack.config.js --mode=development --watch" + }, + "devDependencies": { + "webpack": "^5.99.5", + "webpack-cli": "^6.0.1", + "autoprefixer": "^10.4.21", + "css-loader": "^7.1.2", + "css-minimizer-webpack-plugin": "^7.0.2", + "ignore-emit-webpack-plugin": "^2.0.6", + "mini-css-extract-plugin": "^2.9.2", + "postcss": "^8.5.3", + "postcss-loader": "^8.1.1", + "sass": "^1.86.3", + "sass-loader": "^16.0.5", + "terser-webpack-plugin": "^5.3.14", + "ts-loader": "^9.5.2", + "typescript": "^5.8.3" + } +} \ No newline at end of file diff --git a/src/js/loom.ts b/src/js/loom.ts new file mode 100644 index 0000000..2a03d6e --- /dev/null +++ b/src/js/loom.ts @@ -0,0 +1 @@ +console.log('Welcome to Loom Forge!'); \ No newline at end of file diff --git a/src/scss/_config.scss b/src/scss/_config.scss new file mode 100644 index 0000000..0265499 --- /dev/null +++ b/src/scss/_config.scss @@ -0,0 +1,4 @@ +$spacingLevels: 8; +$fontSizeIncrement: 0.125rem; +$fontSizeBase: 1rem; +$fontLevels: 20; \ No newline at end of file diff --git a/src/scss/core/_display.scss b/src/scss/core/_display.scss new file mode 100644 index 0000000..7595ac2 --- /dev/null +++ b/src/scss/core/_display.scss @@ -0,0 +1,12 @@ +@mixin generateClasses { + .loom- { + &display- { + &-block { + display: block; + } + &-inline-block { + display: inline-block; + } + } + } +} \ No newline at end of file diff --git a/src/scss/core/_spacing.scss b/src/scss/core/_spacing.scss new file mode 100644 index 0000000..ae32a73 --- /dev/null +++ b/src/scss/core/_spacing.scss @@ -0,0 +1,64 @@ +@mixin marginClasses($i, $calc) { + &-m { + &-#{$i} { + margin: $calc; + } + &x-#{$i} { + margin-left: $calc; + margin-right: $calc; + } + &y-#{$i} { + margin-top: $calc; + margin-bottom: $calc; + } + &t-#{$i} { + margin-top: $calc; + } + &b-#{$i} { + margin-bottom: $calc; + } + &l-#{$i} { + margin-left: $calc; + } + &r-#{$i} { + margin-right: $calc; + } + } +} + +@mixin paddingClasses($i, $calc) { + &-p { + &-#{$i} { + padding: $calc; + } + &x-#{$i} { + padding-left: $calc; + padding-right: $calc; + } + &y-#{$i} { + padding-top: $calc; + padding-bottom: $calc; + } + &t-#{$i} { + padding-top: $calc; + } + &b-#{$i} { + padding-bottom: $calc; + } + &l-#{$i} { + padding-left: $calc; + } + &r-#{$i} { + padding-right: $calc; + } + } +} +@mixin generateClasses($spacingLevels) { + @for $i from 1 through $spacingLevels { + $calc: calc(0.25rem * $i); + .loom { + @include marginClasses($i, $calc); + @include paddingClasses($i, $calc); + } + } +} \ No newline at end of file diff --git a/src/scss/loom.scss b/src/scss/loom.scss new file mode 100644 index 0000000..7bd8620 --- /dev/null +++ b/src/scss/loom.scss @@ -0,0 +1,8 @@ +@use 'config'; +@use 'core/display'; +@use 'core/spacing'; +@use 'text/fontSize'; + +@include display.generateClasses(); +@include spacing.generateClasses(config.$spacingLevels); +@include fontSize.generateCleanClasses(config.$fontSizeBase, config.$fontSizeIncrement, config.$fontLevels); \ No newline at end of file diff --git a/src/scss/text/_fontSize.scss b/src/scss/text/_fontSize.scss new file mode 100644 index 0000000..c255fa1 --- /dev/null +++ b/src/scss/text/_fontSize.scss @@ -0,0 +1,33 @@ +@mixin generateNumericClasses($fontSizeStart, $fontSizeIncrement, $levels) { + @for $i from 1 through $levels { + .loom- { + &-text-size-#{$i} { + font-size: calc($fontSizeStart + ($fontSizeIncrement * ($i - 1))); + } + } + } +} + +@mixin generateCleanClasses($fontSizeBase, $fontSizeIncrement, $fontLevels) { + .loom- { + &text-size- { + &xs { + font-size: calc($fontSizeBase - ($fontSizeIncrement * 2)); + } + &sm { + font-size: calc($fontSizeBase - $fontSizeIncrement); + } + &base { + font-size: $fontSizeBase; + } + &l { + font-size: calc($fontSizeBase + $fontSizeIncrement); + } + @for $i from 2 through $fontLevels { + &#{$i}l { + font-size: calc($fontSizeBase + ($fontSizeIncrement * $i)); + } + } + } + } +} \ No newline at end of file diff --git a/templates/base/footer.tmpl b/templates/base/footer.tmpl new file mode 100644 index 0000000..3261470 --- /dev/null +++ b/templates/base/footer.tmpl @@ -0,0 +1,21 @@ +{{if false}} + {{/* to make html structure "likely" complete to prevent IDE warnings */}} +<html> +<body> + <div> +{{end}} + + {{template "custom/body_inner_post" .}} + + </div> + + {{template "custom/body_outer_post" .}} + + {{template "base/footer_content" .}} + + <script src="{{AssetUrlPrefix}}/js/index.js?v={{AssetVersion}}" onerror="alert('{{ctx.Locale.Tr "alert.asset_load_failed"}}'.replace('{path}', this.src))"></script> + <script src="/assets/js/loomScripts.js"></script> + + {{template "custom/footer" .}} +</body> +</html> diff --git a/templates/base/footer_content.tmpl b/templates/base/footer_content.tmpl new file mode 100644 index 0000000..47cd5b9 --- /dev/null +++ b/templates/base/footer_content.tmpl @@ -0,0 +1,30 @@ +<footer class="page-footer" role="group" aria-label="{{ctx.Locale.Tr "aria.footer"}}"> + <div class="left-links" role="contentinfo" aria-label="{{ctx.Locale.Tr "aria.footer.software"}}"> + {{if ShowFooterPoweredBy}} + <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org">{{ctx.Locale.Tr "powered_by" "Forgejo"}}</a> + {{end}} + {{if (or .ShowFooterVersion .PageIsAdmin)}} + {{ctx.Locale.Tr "version"}}: + {{if .IsAdmin}} + <a href="{{AppSubUrl}}/admin/config">{{AppVer}}</a> + {{else}} + {{AppVerNoMetadata}} + {{end}} + {{end}} + {{if and .TemplateLoadTimes ShowFooterTemplateLoadTime}} + {{ctx.Locale.Tr "page"}}: <strong>{{LoadTimes .PageStartTime}}</strong> + {{ctx.Locale.Tr "template"}}{{if .TemplateName}} {{.TemplateName}}{{end}}: <strong>{{call .TemplateLoadTimes}}</strong> + {{end}} + </div> + <div class="right-links" role="group" aria-label="{{ctx.Locale.Tr "aria.footer.links"}}"> + <div class="ui dropdown upward language"> + <span class="flex-text-inline">{{svg "octicon-globe" 14}} {{ctx.Locale.LangName}}</span> + <div class="menu language-menu"> + {{range .AllLangs}} + <a lang="{{.Lang}}" data-url="{{AppSubUrl}}/?lang={{.Lang}}" class="item {{if eq ctx.Locale.Lang .Lang}}active selected{{end}}">{{.Name}}</a> + {{end}} + </div> + </div> + {{template "custom/extra_links_footer" .}} + </div> +</footer> diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl new file mode 100644 index 0000000..c029407 --- /dev/null +++ b/templates/base/head.tmpl @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<html lang="{{ctx.Locale.Lang}}" data-theme="{{ThemeName .SignedUser}}"> +<head> + <meta name="viewport" content="width=device-width, initial-scale=1"> + {{/* Display `- .Repository.FullName` only if `.Title` does not already start with that. */}} + <title>{{if .Title}}{{.Title}} - {{end}}{{if and (.Repository.Name) (not (StringUtils.HasPrefix .Title .Repository.FullName))}}{{.Repository.FullName}} - {{end}}{{AppDisplayName}}</title> + {{if .ManifestData}}<link rel="manifest" href="data:{{.ManifestData}}">{{end}} + <meta name="author" content="{{if .Repository}}{{.Owner.Name}}{{else}}{{MetaAuthor}}{{end}}"> + <meta name="description" content="{{if .Repository}}{{.Repository.Name}}{{if .Repository.Description}} - {{.Repository.Description}}{{end}}{{else}}{{MetaDescription}}{{end}}"> + <meta name="keywords" content="{{MetaKeywords}}"> + <meta name="referrer" content="no-referrer"> +{{if .GoGetImport}} + <meta name="go-import" content="{{.GoGetImport}} git {{.RepoCloneLink.HTTPS}}"> + <meta name="go-source" content="{{.GoGetImport}} _ {{.GoDocDirectory}} {{.GoDocFile}}"> +{{end}} +{{if and .EnableFeed .FeedURL}} + <link rel="alternate" type="application/atom+xml" title="" href="{{.FeedURL}}.atom"> + <link rel="alternate" type="application/rss+xml" title="" href="{{.FeedURL}}.rss"> +{{end}} + <link rel="icon" href="{{AssetUrlPrefix}}/img/favicon.svg" type="image/svg+xml"> + <link rel="alternate icon" href="{{AssetUrlPrefix}}/img/favicon.png" type="image/png"> + {{template "base/head_script" .}} + {{template "shared/user/mention_highlight" .}} + {{template "base/head_opengraph" .}} + {{template "base/head_style" .}} + <link rel="stylesheet" type="text/css" href="/assets/css/loomStyles.css"> + {{template "custom/header" .}} +</head> +<body hx-headers='{"x-csrf-token": "{{.CsrfToken}}"}' hx-swap="outerHTML" hx-ext="morph" hx-push-url="false"> + {{template "custom/body_outer_pre" .}} + + <div class="full height"> + <noscript>{{ctx.Locale.Tr "enable_javascript"}}</noscript> + + {{template "custom/body_inner_pre" .}} + + {{if not .PageIsInstall}} + {{template "base/head_navbar" .}} + {{end}} + +{{if false}} + {{/* to make html structure "likely" complete to prevent IDE warnings */}} + </div> +</body> +</html> +{{end}} diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl new file mode 100644 index 0000000..0bd25ea --- /dev/null +++ b/templates/base/head_navbar.tmpl @@ -0,0 +1,203 @@ +{{$notificationUnreadCount := 0}} +{{if and .IsSigned .NotificationUnreadCount}} + {{$notificationUnreadCount = call .NotificationUnreadCount}} +{{end}} + +<nav id="navbar" aria-label="{{ctx.Locale.Tr "aria.navbar"}}"> + <div class="navbar-left ui secondary menu"> + <!-- the logo --> + <a class="item" id="navbar-logo" href="{{AppSubUrl}}/" aria-label="{{if .IsSigned}}{{ctx.Locale.Tr "dashboard"}}{{else}}{{ctx.Locale.Tr "home"}}{{end}}"> + <img width="30" height="30" src="{{AssetUrlPrefix}}/img/logo.svg" alt="{{ctx.Locale.Tr "logo"}}" aria-hidden="true"> + </a> + + <!-- mobile right menu, it must be here because in mobile view, each item is a flex column, the first item is a full row column --> + <div class="ui secondary menu item navbar-mobile-right only-mobile"> + {{if .IsSigned}} + <a id="mobile-notifications-icon" class="item tw-w-auto tw-p-2" href="{{AppSubUrl}}/notifications" data-tooltip-content="{{ctx.Locale.Tr "notifications"}}" aria-label="{{ctx.Locale.Tr "notifications"}}"> + <div class="tw-relative"> + {{svg "octicon-bell"}} + <span class="notification_count{{if not $notificationUnreadCount}} tw-hidden{{end}}">{{$notificationUnreadCount}}</span> + </div> + </a> + {{end}} + <button class="item tw-w-auto ui icon mini button tw-p-2 tw-m-0" id="navbar-expand-toggle" aria-label="{{ctx.Locale.Tr "toggle_menu"}}">{{svg "octicon-three-bars"}}</button> + </div> + + <!-- navbar links non-mobile --> + {{if and .IsSigned .MustChangePassword}} + {{/* No links */}} + {{else if .IsSigned}} + {{if not .UnitIssuesGlobalDisabled}} + <a class="item{{if .PageIsIssues}} active{{end}}" href="{{AppSubUrl}}/issues">{{ctx.Locale.Tr "issues"}}</a> + {{end}} + {{if not .UnitPullsGlobalDisabled}} + <a class="item{{if .PageIsPulls}} active{{end}}" href="{{AppSubUrl}}/pulls">{{ctx.Locale.Tr "pull_requests"}}</a> + {{end}} + {{if not (and .UnitIssuesGlobalDisabled .UnitPullsGlobalDisabled)}} + {{if .ShowMilestonesDashboardPage}} + <a class="item{{if .PageIsMilestonesDashboard}} active{{end}}" href="{{AppSubUrl}}/milestones">{{ctx.Locale.Tr "milestones"}}</a> + {{end}} + {{end}} + <a class="item{{if .PageIsExplore}} active{{end}}" href="{{AppSubUrl}}/explore/repos">{{ctx.Locale.Tr "explore"}}</a> + {{else if .IsLandingPageOrganizations}} + <a class="item{{if .PageIsExplore}} active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{ctx.Locale.Tr "explore"}}</a> + {{else}} + <a class="item{{if .PageIsExplore}} active{{end}}" href="{{AppSubUrl}}/explore/repos">{{ctx.Locale.Tr "explore"}}</a> + {{end}} + + {{template "custom/extra_links" .}} + </div> + + <!-- the full dropdown menus --> + <div class="navbar-right ui secondary menu"> + {{if and .IsSigned .MustChangePassword}} + <div class="ui dropdown jump item" data-tooltip-content="{{ctx.Locale.Tr "user_profile_and_more"}}"> + <span class="text tw-flex tw-items-center"> + {{ctx.AvatarUtils.Avatar .SignedUser 24 "tw-mr-1"}} + <span class="only-mobile tw-ml-2">{{.SignedUser.Name}}</span> + <span class="not-mobile">{{svg "octicon-triangle-down"}}</span> + </span> + <div class="menu user-menu"> + <div class="ui header"> + {{ctx.Locale.Tr "signed_in_as"}} <strong>{{.SignedUser.Name}}</strong> + </div> + + <div class="divider"></div> + <a class="item link-action" href data-url="{{AppSubUrl}}/user/logout"> + {{svg "octicon-sign-out"}} + {{ctx.Locale.Tr "sign_out"}} + </a> + </div><!-- end content avatar menu --> + </div><!-- end dropdown avatar menu --> + {{else if .IsSigned}} + {{if EnableTimetracking}} + <a class="active-stopwatch-trigger item tw-mx-0{{if not .ActiveStopwatch}} tw-hidden{{end}}" href="{{.ActiveStopwatch.IssueLink}}" title="{{ctx.Locale.Tr "active_stopwatch"}}"> + <div class="tw-relative"> + {{svg "octicon-stopwatch"}} + <span class="header-stopwatch-dot"></span> + </div> + <span class="only-mobile tw-ml-2">{{ctx.Locale.Tr "active_stopwatch"}}</span> + </a> + <div class="active-stopwatch-popup item tippy-target tw-p-2"> + <div class="tw-flex tw-items-center"> + <a class="stopwatch-link tw-flex tw-items-center" href="{{.ActiveStopwatch.IssueLink}}"> + {{svg "octicon-issue-opened" 16 "tw-mr-2"}} + <span class="stopwatch-issue">{{.ActiveStopwatch.RepoSlug}}#{{.ActiveStopwatch.IssueIndex}}</span> + <span class="ui primary label stopwatch-time tw-my-0 tw-mx-4" data-seconds="{{.ActiveStopwatch.Seconds}}"> + {{if .ActiveStopwatch}}{{Sec2Time .ActiveStopwatch.Seconds}}{{end}} + </span> + </a> + <form class="stopwatch-commit" method="post" action="{{.ActiveStopwatch.IssueLink}}/times/stopwatch/toggle"> + {{.CsrfTokenHtml}} + <button + type="submit" + class="ui button mini compact basic icon" + data-tooltip-content="{{ctx.Locale.Tr "repo.issues.stop_tracking"}}" + >{{svg "octicon-square-fill"}}</button> + </form> + <form class="stopwatch-cancel" method="post" action="{{.ActiveStopwatch.IssueLink}}/times/stopwatch/cancel"> + {{.CsrfTokenHtml}} + <button + type="submit" + class="ui button mini compact basic icon" + data-tooltip-content="{{ctx.Locale.Tr "repo.issues.cancel_tracking"}}" + >{{svg "octicon-trash"}}</button> + </form> + </div> + </div> + {{end}} + + <a class="item not-mobile tw-mx-0" href="{{AppSubUrl}}/notifications" data-tooltip-content="{{ctx.Locale.Tr "notifications"}}" aria-label="{{ctx.Locale.Tr "notifications"}}"> + <div class="tw-relative"> + {{svg "octicon-bell"}} + <span class="notification_count{{if not $notificationUnreadCount}} tw-hidden{{end}}">{{$notificationUnreadCount}}</span> + </div> + </a> + + <div class="ui dropdown jump item tw-mx-0 tw-pr-2" data-tooltip-content="{{ctx.Locale.Tr "create_new"}}"> + <span class="text"> + {{svg "octicon-plus"}} + <span class="not-mobile">{{svg "octicon-triangle-down"}}</span> + <span class="only-mobile">{{ctx.Locale.Tr "create_new"}}</span> + </span> + <div class="menu"> + <a class="item" href="{{AppSubUrl}}/repo/create"> + {{svg "octicon-plus"}} {{ctx.Locale.Tr "new_repo.link"}} + </a> + {{if not .DisableMigrations}} + <a class="item" href="{{AppSubUrl}}/repo/migrate"> + {{svg "octicon-repo-push"}} {{ctx.Locale.Tr "new_migrate.link"}} + </a> + {{end}} + {{if .SignedUser.CanCreateOrganization}} + <a class="item" href="{{AppSubUrl}}/org/create"> + {{svg "octicon-organization"}} {{ctx.Locale.Tr "new_org.link"}} + </a> + {{end}} + </div><!-- end content create new menu --> + </div><!-- end dropdown menu create new --> + + <div class="ui dropdown jump item tw-mx-0 tw-pr-2" data-tooltip-content="{{ctx.Locale.Tr "user_profile_and_more"}}"> + <span class="text tw-flex tw-items-center"> + {{ctx.AvatarUtils.Avatar .SignedUser 24 "tw-mr-1"}} + <span class="only-mobile tw-ml-2">{{.SignedUser.Name}}</span> + <span class="not-mobile">{{svg "octicon-triangle-down"}}</span> + </span> + <div class="menu user-menu"> + <div class="ui header"> + {{ctx.Locale.Tr "signed_in_as"}} <strong>{{.SignedUser.Name}}</strong> + </div> + + <div class="divider"></div> + <a class="item" href="{{.SignedUser.HomeLink}}"> + {{svg "octicon-person"}} + {{ctx.Locale.Tr "your_profile"}} + </a> + {{if not .DisableStars}} + <a class="item" href="{{.SignedUser.HomeLink}}?tab=stars"> + {{svg "octicon-star"}} + {{ctx.Locale.Tr "your_starred"}} + </a> + {{end}} + <a class="item" href="{{AppSubUrl}}/notifications/subscriptions"> + {{svg "octicon-bell"}} + {{ctx.Locale.Tr "notification.subscriptions"}} + </a> + <a class="{{if .PageIsUserSettings}}active {{end}}item" href="{{AppSubUrl}}/user/settings"> + {{svg "octicon-tools"}} + {{ctx.Locale.Tr "your_settings"}} + </a> + <a class="item" target="_blank" rel="noopener noreferrer" href="https://forgejo.org/docs/latest/"> + {{svg "octicon-question"}} + {{ctx.Locale.Tr "help"}} + </a> + {{if .IsAdmin}} + <div class="divider"></div> + + <a class="{{if .PageIsAdmin}}active {{end}}item" href="{{AppSubUrl}}/admin"> + {{svg "octicon-server"}} + {{ctx.Locale.Tr "admin_panel"}} + </a> + {{end}} + + <div class="divider"></div> + <a class="item link-action" href data-url="{{AppSubUrl}}/user/logout"> + {{svg "octicon-sign-out"}} + {{ctx.Locale.Tr "sign_out"}} + </a> + </div><!-- end content avatar menu --> + </div><!-- end dropdown avatar menu --> + {{else}} + {{if .ShowRegistrationButton}} + <a class="item{{if .PageIsSignUp}} active{{end}}" href="{{AppSubUrl}}/user/sign_up"> + {{svg "octicon-person" 16 "tw-mr-1"}} + <span>{{ctx.Locale.Tr "register"}}</span> + </a> + {{end}} + <a class="item{{if .PageIsSignIn}} active{{end}}" rel="nofollow" href="{{AppSubUrl}}/user/login{{if not .PageIsSignIn}}?redirect_to={{.CurrentURL}}{{end}}"> + {{svg "octicon-sign-in" 16 "tw-mr-1"}} + <span>{{ctx.Locale.Tr "sign_in"}}</span> + </a> + {{end}} + </div><!-- end full right menu --> +</nav> diff --git a/templates/home.tmpl b/templates/home.tmpl index 9cc9153..a6af086 100644 --- a/templates/home.tmpl +++ b/templates/home.tmpl @@ -1,8 +1,8 @@ {{template "base/head" .}} <div role="main" aria-label="Welcome" class="page-content home"> - <div class="tw-mb-8 tw-px-8 center"> - <div class="hero"> - <h1 class="ui icon header title"> + <div class="center"> + <div> + <h1 class="tw-font-bold loom-text-size-16l"> Loom Forge </h1> </div> diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..6d0fe5f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "outDir": "./public/assets/js", + "noImplicitAny": true, + "module": "commonjs", + "target": "es6", + "allowJs": true, + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["./src/js/loom.ts"] +} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..9600fb0 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,77 @@ +const path = require('path'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const IgnoreEmitPlugin = require('ignore-emit-webpack-plugin'); +const TerserPlugin = require('terser-webpack-plugin'); +const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); + +const entries = { + loomScripts: './src/js/loom.ts', + loomStyles: './src/scss/loom.scss', +} +const ignoreFiles = Object.keys(entries).reduce((acc, key) => { + if (entries[key].endsWith('.scss')) { + acc.push(`${key}.js`); + } + + return acc; +}, []); + +module.exports = (env, options) => { + const isProduction = options.mode === 'production'; + + return { + entry: entries, + module: { + rules: [ + { + test: /\.ts?$/, + use: 'ts-loader', + exclude: /node_modules/ + }, + { + test: /\.s[ac]ss$/i, + use: [ + MiniCssExtractPlugin.loader, + 'css-loader', + { + loader: 'postcss-loader', + options: { + postcssOptions: { + plugins: [ + require('autoprefixer') + ] + } + } + }, + 'sass-loader' + ], + exclude: /node_modules/ + }, + ], + }, + plugins: [ + new MiniCssExtractPlugin({ + filename: 'css/[name].css', + }), + new IgnoreEmitPlugin(ignoreFiles) + ], + optimization: isProduction ? { + minimize: true, + minimizer: [ + new TerserPlugin(), + new CssMinimizerPlugin(), + ] + } : {}, + resolve: { + extensions: ['.ts', '.js', '.css', '.scss'] + }, + watchOptions: { + poll: true, + ignored: /node_modules/ + }, + output: { + filename: 'js/[name].js', + path: path.resolve(__dirname, 'public/assets/') + } + }; +} \ No newline at end of file