├── .gitignore
├── LICENSE
├── Readme.md
├── index.html
├── package.json
├── resources
├── img
│ └── favicon-src.png
└── locales
│ └── en-GB
│ └── en-GB.json
├── src
├── Application.js
├── bootstrap.js
├── core
│ └── field
│ │ └── Combobox.js
├── overrides
│ ├── Dialog.js
│ └── index.js
└── view
│ ├── Main.js
│ └── Main.scss
├── themes
└── theme-material
│ ├── all-debug.js
│ └── all.scss
├── webpack
├── common.js
├── css.config.js
├── dev.js
├── esbuild.config.js
├── postcss.config.js
├── prod-server.js
├── prod.js
└── settings.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /build
3 | /ext
4 | node_modules
5 | package-lock.json
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Don Griffin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # Ext JS webpack App
2 |
3 | A simple demonstration of using [webpack](https://webpack.js.org/) to build an
4 | app bundle to go along with the pre-built Ext JS library.
5 |
6 | The goal here is to use fully modern build tools, avoiding Sencha Cmd, so that
7 | we get `import`, `export` and sourcemaps.
8 |
9 | ## Getting Started
10 |
11 | Make an `ext` folder that links to your Ext JS download content. In the
12 | checkout directory, run a command like this:
13 |
14 | $ ln -s ../../sdks/ext-6.5.3 ext
15 |
16 | On Windows:
17 |
18 | > mklink /D ext ..\..\sdks\ext-6.5.3
19 |
20 | As alternative, in case you need a custom ExtJS build (like upgrading FontAwesome to Pro),
21 | in the ExtJS release directory `ext-6.5.3/` run `sench package build` to build ExtJS files. Then after build,
22 | you can copy in `project/ext/build` the `ext-6.5.3/build/modern`, `ext-6.5.3/build/packages`,
23 | and `ext-6.5.3/build/ext-modern-all.js`, `-debug.js`.
24 | files into your project.
25 |
26 | Then run `yarn run build`:
27 |
28 | $ yarn run build
29 |
30 | > app@2.0.0 build ..../extjs-webpack
31 | > webpack
32 |
33 | Hash: 514ce3b213ceda0e6dbe
34 | Version: webpack 4.42.1
35 | Time: 99ms
36 | Built at: 04/05/2020 6:17:04 PM
37 | Asset Size Chunks Chunk Names
38 | bundle.js 4.98 KiB main [emitted] main
39 | Entrypoint main = bundle.js
40 | [./src/app.js] 300 bytes {main} [built]
41 | [./src/view/Main.js] 245 bytes {main} [built]
42 |
43 | That's all. Now load in browser.
44 |
45 | ## Going Forward
46 |
47 | First thing to note is that all app files are modules and you `import` them. They can
48 | export their constructors like I did in `Main.js`:
49 |
50 | export default
51 | Ext.define('App.view.Main, {
52 | ...
53 | });
54 |
55 | The `Ext.define()` function needs to be used to extend Ext JS classes and it returns
56 | the class constructor. We export it so that other modules can get it in an ES6 way
57 | as I do in `app.js`:
58 |
59 | import Main from './view/Main.js'
60 |
61 | The class is still placed on the global scope as `App.view.Main`, but following the
62 | ES6 way will clarify relationships for the bundler.
63 |
64 | ### Strict Mode
65 |
66 | Modules use strict mode, so be aware that `callParent` cannot be called from a
67 | function declared in such places. This is easy to work around as I do in `Main`:
68 |
69 | const Panel = Ext.Panel;
70 | const superCls = Panel.prototype;
71 |
72 | export default
73 | Ext.define('App.view.Main', {
74 | extend: Panel,
75 |
76 | title : 'App',
77 |
78 | constructor(...args) {
79 | superCls.constructor.call(this, ...args);
80 | }
81 | });
82 |
83 | Normally, in Ext JS, the `constructor` override would have used `callParent` to
84 | call the `Panel` super class.
85 |
86 | ## Conclusion
87 |
88 | Using Sencha Themer would be great partner here. Just build your theme and link
89 | it in via the index file.
90 |
91 | I'd love to hear what folks think of this approach!
92 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | App
8 |
9 |
10 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app",
3 | "version": "4.0.0",
4 | "description": "Ext JS webpack App",
5 | "main": "webpack.config.js",
6 | "scripts": {
7 | "dev": "webpack serve --config webpack/dev.js --node-env=development",
8 | "build": "webpack --config webpack/prod.js --progress --node-env=production",
9 | "prod": "webpack serve --config webpack/prod-server.js",
10 | "cy:open": "cypress open",
11 | "cy:run": "cypress run"
12 | },
13 | "author": "Vadim Popa",
14 | "license": "MIT",
15 | "engines": {
16 | "node": ">=16.13.0",
17 | "npm": ">=8.1.0"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git@github.com:vadimpopa/extjs-webpack.git"
22 | },
23 | "devDependencies": {
24 | "@babel/core": "^7.16.0",
25 | "@babel/preset-env": "^7.16.4",
26 | "@cypress/webpack-dev-server": "^1.7.0",
27 | "@gfx/zopfli": "^1.0.15",
28 | "@vue/preload-webpack-plugin": "^2.0.0",
29 | "babel-loader": "^8.2.3",
30 | "clean-webpack-plugin": "^4.0.0",
31 | "copy-webpack-plugin": "^10.0.0",
32 | "core-js": "^3.19.3",
33 | "css-loader": "^6.5.1",
34 | "cssnano": "^5.0.12",
35 | "cypress": "9.2.0",
36 | "cypress-webpack-preprocessor-v5": "5.0.0-alpha.1",
37 | "dotenv": "^10.0.0",
38 | "esbuild-loader": "^2.16.0",
39 | "exports-loader": "^3.1.0",
40 | "git-rev-sync": "^3.0.1",
41 | "html-webpack-plugin": "^5.5.0",
42 | "husky": "7.0.4",
43 | "imports-loader": "^3.1.1",
44 | "lint-staged": "^12.1.2",
45 | "mini-css-extract-plugin": "^2.4.5",
46 | "moment": "^2.29.1",
47 | "postcss": "^8.4.4",
48 | "postcss-import": "^14.0.2",
49 | "postcss-loader": "^6.2.1",
50 | "postcss-preset-env": "^7.0.1",
51 | "prettier": "^2.5.1",
52 | "pretty-quick": "3.1.3",
53 | "sass": "^1.45.2",
54 | "sass-loader": "^12.4.0",
55 | "style-loader": "^3.3.1",
56 | "symlink-webpack-plugin": "^1.0.0",
57 | "terser-webpack-plugin": "^5.2.5",
58 | "val-loader": "^4.0.0",
59 | "webpack": "^5.65.0",
60 | "webpack-bundle-analyzer": "^4.5.0",
61 | "webpack-cli": "^4.9.1",
62 | "webpack-dev-server": "4.7.2",
63 | "webpack-merge": "^5.8.0",
64 | "webpack-notifier": "^1.14.1",
65 | "webpack-strip-block": "^0.3.0"
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/resources/img/favicon-src.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vadimpopa/extjs-webpack/7cfeaaf25ed5126447e0237599e5a39804150620/resources/img/favicon-src.png
--------------------------------------------------------------------------------
/resources/locales/en-GB/en-GB.json:
--------------------------------------------------------------------------------
1 | {
2 | "appTitle": "My App",
3 | "wantDarkTheme": "Want Dark theme ?"
4 | }
--------------------------------------------------------------------------------
/src/Application.js:
--------------------------------------------------------------------------------
1 | import './view/Main.js';
2 |
3 | export default Ext.define('MyApp.Application', {
4 | extend: 'Ext.app.Application',
5 |
6 | namespaces: 'MyApp',
7 | name: 'MyApp',
8 | mainView: 'MyApp.view.Main',
9 |
10 | setStateItem(item, value) {
11 | debugger
12 | MyApp.Boot.getAppStateStorage().setItem(item, value);
13 | },
14 | });
15 |
--------------------------------------------------------------------------------
/src/bootstrap.js:
--------------------------------------------------------------------------------
1 | (async () => {
2 | const bootstrap = {
3 | lang: '',
4 | appName: 'MyApp',
5 | translations: {},
6 |
7 | /**
8 | *
9 | * @return {Object}
10 | */
11 | getAppStateStorage() {
12 | return Ext.util.LocalStorage.get(`${bootstrap.appName}State`);
13 | },
14 |
15 | getApp() {
16 | let app = window[bootstrap.appName];
17 |
18 | if (!app) {
19 | app = window[bootstrap.appName] = {};
20 | }
21 |
22 | return app;
23 | },
24 |
25 | getNavigatorLanguages() {
26 | let found = [];
27 |
28 | if (typeof navigator !== 'undefined') {
29 | if (navigator.languages) {
30 | // chrome only; not an array, so can't use .push.apply instead of iterating
31 | for (let i = 0; i < navigator.languages.length; i++) {
32 | found.push(navigator.languages[i]);
33 | }
34 | }
35 | if (navigator.userLanguage) {
36 | found.push(navigator.userLanguage);
37 | }
38 | if (navigator.language) {
39 | found.push(navigator.language);
40 | }
41 | }
42 |
43 | return found.length > 0 ? found : undefined;
44 | },
45 |
46 | /**
47 | * @return {String}
48 | */
49 | getLang() {
50 | let {lang} = bootstrap;
51 |
52 | if (!lang) {
53 | // First check localeStorage for saved language
54 | const localStorageInstance = bootstrap.getAppStateStorage();
55 | const isAutoDetectLanguage = true;
56 |
57 | lang = localStorageInstance.getItem('lang');
58 | localStorageInstance.release();
59 |
60 | // If saved language is not found in locales auto detect from OS or default
61 | if (!lang) {
62 | if (isAutoDetectLanguage) {
63 | const languages = bootstrap.getNavigatorLanguages();
64 | const found = languages.find((lang) => lang);
65 |
66 | if (found) {
67 | lang = found;
68 | }
69 | }
70 | }
71 |
72 | if (!lang) {
73 | lang = 'en';
74 | }
75 |
76 | document.documentElement.setAttribute('lang', lang);
77 |
78 | bootstrap.lang = lang;
79 | }
80 |
81 | return lang;
82 | },
83 |
84 | initLang() {
85 | bootstrap.lang = '';
86 | bootstrap.getLang();
87 | },
88 |
89 | isDarkMode() {
90 | const localStorageInstance = bootstrap.getAppStateStorage();
91 | let darkMode = localStorageInstance.getItem('theme-dark-mode');
92 | localStorageInstance.release();
93 |
94 | return darkMode === 'true';
95 | },
96 |
97 | loadTranslations() {
98 | const me = this;
99 | const {lang} = me;
100 |
101 | return new Promise((resolve, reject) => {
102 | Ext.Ajax.request({
103 | url: `resources/locales/${lang}/${lang}.json`,
104 | callback(options, success, response) {
105 | const data = Ext.decode(response.responseText, true);
106 |
107 | if (data) {
108 | me.translations = data;
109 | resolve();
110 | } else {
111 | reject();
112 | }
113 | },
114 | });
115 | }).catch(() => {
116 | const m = new Ext.MessageBox();
117 | m.alert('Failure', `Couldn't load files`);
118 | });
119 | },
120 |
121 | i18nInit({default: i18next}) {
122 | const me = this;
123 | const {lang} = me;
124 |
125 | return i18next
126 | .init({
127 | lng: lang,
128 | debug: true,
129 | fallbackLng: false,
130 | returnEmptyString: false,
131 | interpolation: {
132 | prefix: '{{',
133 | suffix: '}}',
134 | },
135 | ns: ['app'],
136 | nsSeparator: false,
137 | keySeparator: false,
138 | defaultNS: 'app',
139 | })
140 | .then(() => {
141 | i18next.addResourceBundle(lang, 'app', me.translations);
142 |
143 | const app = bootstrap.getApp();
144 |
145 | app.i18next = i18next;
146 | app.t = i18next.t.bind(i18next);
147 | me.translations = null;
148 | });
149 | }
150 | };
151 |
152 | let themeName;
153 | let environment;
154 |
155 | const Ext = await import(/* webpackChunkName: "ext" */ 'Ext');
156 | const app = bootstrap.getApp();
157 | const {appName} = bootstrap;
158 | const localStorageInstance = bootstrap.getAppStateStorage();
159 |
160 | Ext.ns(appName);
161 |
162 | app.Boot = bootstrap;
163 | app.isDarkMode = bootstrap.isDarkMode;
164 |
165 | themeName = localStorageInstance.getItem('theme') || 'theme-material';
166 | environment = process.env.NODE_ENV;
167 |
168 | const {manifest} = Ext;
169 |
170 | manifest.env = environment;
171 | manifest.isDevelopment = environment === 'development';
172 |
173 | await import(
174 | /* webpackChunkName: '[request]' */
175 | `../themes/${themeName}/all${
176 | environment === 'development' ? '-debug' : ''
177 | }.js`
178 | ).then(bootstrap.initLang)
179 |
180 | await bootstrap.loadTranslations();
181 |
182 | await import('i18next').then(bootstrap.i18nInit.bind(bootstrap));
183 |
184 | await import(/* webpackChunkName: "app" */ './overrides/index');
185 | await import(/* webpackChunkName: "app" */ './Application');
186 |
187 | Ext.application(`${appName}.Application`);
188 | })();
189 |
--------------------------------------------------------------------------------
/src/core/field/Combobox.js:
--------------------------------------------------------------------------------
1 | export default Ext.define('App.core.field.Combobox', {
2 | extend: 'Ext.field.ComboBox',
3 |
4 | clearable: true,
5 | forceSelection: true,
6 |
7 | config: {
8 | allDisplayTpl: false
9 | },
10 | allDisplayTplRegex: /[^{\}]+(?=})/g,
11 |
12 | applyAllDisplayTpl(tpl) {
13 | if (tpl !== false) {
14 | this.setItemTpl(tpl);
15 | this.setDisplayTpl(tpl);
16 | this.setPrimaryFilter(new Ext.util.Filter({
17 | property: tpl.match(this.allDisplayTplRegex),
18 | filterFn: this.allFilterFn
19 | }));
20 | }
21 | return tpl;
22 | },
23 |
24 | allFilterFn(candidateRecord) {
25 | // This called in the scope of the Filter instance, we have this config
26 | let searchValue = this.getValue();
27 |
28 | if (searchValue) {
29 | searchValue = searchValue.toLowerCase();
30 |
31 | return this.getProperty().some(field => {
32 | const value = (candidateRecord.get(field) || '').toLowerCase();
33 | return value.indexOf(searchValue) > -1;
34 | });
35 | }
36 | return false;
37 | }
38 | });
39 |
--------------------------------------------------------------------------------
/src/overrides/Dialog.js:
--------------------------------------------------------------------------------
1 | const Dialog = Ext.Dialog;
2 | const superCls = Dialog.prototype;
3 | const initialize = superCls.initialize;
4 |
5 | Ext.define(null, {
6 | override: 'Ext.Dialog',
7 |
8 | initialize() {
9 | initialize.call(this);
10 |
11 | if (this.autoShow) {
12 | this.show();
13 | }
14 | },
15 | });
16 |
--------------------------------------------------------------------------------
/src/overrides/index.js:
--------------------------------------------------------------------------------
1 | import './Dialog';
--------------------------------------------------------------------------------
/src/view/Main.js:
--------------------------------------------------------------------------------
1 | import './Main.scss';
2 | import '../core/field/Combobox';
3 |
4 | const Panel = Ext.Panel;
5 | const superclass = Panel.prototype;
6 |
7 | export default Ext.define('MyApp.view.Main', {
8 | extend: Panel,
9 |
10 | title: MyApp.t('appTitle'),
11 |
12 | html: `Left Panel, 1/3rd of total size>
`,
13 |
14 | constructor(...args) {
15 | superclass.constructor.call(this, ...args);
16 | },
17 |
18 | items: [
19 | {
20 | xtype: 'checkbox',
21 | boxLabel: MyApp.t('wantDarkTheme'),
22 | listeners: {
23 | change(field, isDarkMode) {
24 | debugger
25 | MyApp.app.setStateItem('theme-dark-mode', isDarkMode);
26 | }
27 | }
28 | },
29 | {
30 | xclass: 'App.core.field.Combobox',
31 | width: 400,
32 | margin: 5,
33 | label: 'Choose Language',
34 | queryMode: 'local',
35 | displayField: 'name',
36 | valueField: 'abbr',
37 | allDisplayTpl: '{abbr} {name}',
38 | store: [
39 | {abbr: 'AL', name: 'Alabama'},
40 | {abbr: 'AK', name: 'Alaska'},
41 | {abbr: 'AZ', name: 'Arizona'}
42 | ]
43 | }
44 | ]
45 | });
46 |
--------------------------------------------------------------------------------
/src/view/Main.scss:
--------------------------------------------------------------------------------
1 | .my-class-from-sass {
2 | color: #0a0a0a;
3 | }
--------------------------------------------------------------------------------
/themes/theme-material/all-debug.js:
--------------------------------------------------------------------------------
1 | import '../../ext/build/modern/theme-material/resources/theme-material-all-debug.css';
2 | import './all.scss';
3 | import '../../ext/build/modern/theme-material/theme-material-debug.js';
4 | import '../../ext/build/modern/theme-material/resources/css-vars.js';
5 |
--------------------------------------------------------------------------------
/themes/theme-material/all.scss:
--------------------------------------------------------------------------------
1 | .myapp-dark-mode {
2 | background-color: grey;
3 | }
--------------------------------------------------------------------------------
/webpack/common.js:
--------------------------------------------------------------------------------
1 | // webpack.common.js - common webpack config
2 | // node modules
3 | const path = require('path');
4 |
5 | // webpack plugins
6 | const CopyWebpackPlugin = require('copy-webpack-plugin');
7 | const WebpackNotifierPlugin = require('webpack-notifier');
8 |
9 | // config files
10 | const pkg = require('../package.json');
11 | const settings = require('./settings.js');
12 | const { configureESBuildLoader } = require('./esbuild.config.js');
13 | const { paths } = settings;
14 |
15 | // Configure Entries
16 | const configureEntries = () => {
17 | let entries = {};
18 | for (const [key, value] of Object.entries(settings.entries)) {
19 | entries[key] = path.resolve(__dirname, settings.paths.src + value);
20 | }
21 |
22 | return entries;
23 | };
24 |
25 | const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin');
26 |
27 | // Common module exports
28 | // noinspection WebpackConfigHighlighting
29 | module.exports = (isProd) => {
30 | return {
31 | name: pkg.name,
32 | entry: configureEntries(),
33 | target: 'web',
34 | output: {
35 | path: path.resolve(__dirname, paths.dist.base),
36 | publicPath: './',
37 | },
38 | plugins: [
39 | new PreloadWebpackPlugin({
40 | rel: 'preload',
41 | as: 'font',
42 | include: 'allAssets',
43 | fileWhitelist: [
44 | /resources\/fonts(\/.*).(woff2?|ttf|woff)(\?.*)?$/i,
45 | ],
46 | fileBlacklist: [/codicon.ttf/],
47 | }),
48 | new WebpackNotifierPlugin({
49 | title: 'Webpack',
50 | excludeWarnings: true,
51 | alwaysNotify: true,
52 | }),
53 | new CopyWebpackPlugin({
54 | patterns: settings.copyFiles,
55 | }),
56 | ],
57 | resolve: {
58 | alias: {
59 | '@': path.resolve(__dirname, settings.paths.src),
60 | Ext: path.resolve(
61 | __dirname,
62 | `${paths.Ext}ext-modern-all${isProd ? '' : '-debug'}.js`
63 | ),
64 | },
65 | fallback: {
66 | fs: false,
67 | },
68 | },
69 | module: {
70 | rules: [configureESBuildLoader()],
71 | },
72 | };
73 | };
74 |
--------------------------------------------------------------------------------
/webpack/css.config.js:
--------------------------------------------------------------------------------
1 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
2 |
3 | module.exports = {
4 | configureCssLoaders() {
5 | return [
6 | {
7 | test: /\.((c|sa|sc)ss)$/i,
8 | exclude: [/ext\/build/],
9 | use: [
10 | // fallback to style-loader in development
11 | process.env.NODE_ENV !== 'production'
12 | ? 'style-loader'
13 | : MiniCssExtractPlugin.loader,
14 | {
15 | loader: 'css-loader',
16 | options: {
17 | url: false,
18 | importLoaders: 1,
19 | sourceMap: true,
20 | modules: {
21 | auto: true,
22 | mode: 'local',
23 | exportGlobals: true,
24 | localIdentName:
25 | '[name]__[local]--[hash:base64:5]',
26 | },
27 | },
28 | },
29 | {
30 | loader: 'sass-loader',
31 | options: {
32 | sourceMap: true,
33 | },
34 | },
35 | ],
36 | },
37 | {
38 | test: /\.(css)$/,
39 | include: [/ext/],
40 | use: [
41 | // fallback to style-loader in development
42 | process.env.NODE_ENV !== 'production'
43 | ? 'style-loader'
44 | : MiniCssExtractPlugin.loader,
45 | {
46 | loader: 'css-loader',
47 | options: {
48 | url: true,
49 | },
50 | },
51 | ],
52 | },
53 | {
54 | test: /\.(woff|woff2|svg|eot|ttf|otf)$/i,
55 | type: 'asset/resource',
56 | generator: {
57 | filename: 'resources/fonts/[name].[contenthash][ext]',
58 | publicPath: '../../',
59 | },
60 | },
61 | {
62 | test: /\.(png|jpg|jpeg|gif)$/i,
63 | type: 'asset/resource',
64 | generator: {
65 | filename: 'resources/images/[name].[contenthash][ext]',
66 | publicPath: '../../',
67 | },
68 | },
69 | ];
70 | },
71 | };
72 |
--------------------------------------------------------------------------------
/webpack/dev.js:
--------------------------------------------------------------------------------
1 | // webpack.dev.js - developmental builds
2 |
3 | // node modules
4 | const { merge } = require('webpack-merge');
5 | const path = require('path');
6 | const webpack = require('webpack');
7 | const HtmlWebpackPlugin = require('html-webpack-plugin');
8 |
9 | // config files
10 | const commonConfig = require('./common.js');
11 | const { devServerConfig } = require('./settings.js');
12 |
13 | // Configure the webpack-dev-server
14 | const configureDevServer = () => {
15 | return {
16 | host: devServerConfig.host(),
17 | port: devServerConfig.port(),
18 | https: !!parseInt(devServerConfig.https()),
19 | hot: false,
20 | client: {
21 | overlay: {
22 | warnings: true,
23 | errors: true,
24 | },
25 | },
26 | liveReload: true,
27 | static: {
28 | directory: path.resolve(__dirname, '../'),
29 | publicPath: './',
30 | },
31 | headers: {
32 | 'Access-Control-Allow-Origin': '*',
33 | },
34 |
35 | // Use it to run a proxy
36 | // proxy: {
37 | // '/api': {
38 | // target: devServerConfig.remoteServerUrl,
39 | // ws: true,
40 | // secure: false,
41 | // cookieDomainRewrite: false,
42 | // cookiePathRewrite: {
43 | // '/path/': '/',
44 | // '/path': '/',
45 | // },
46 | // debug: true,
47 | // changeOrigin: true,
48 | // onProxyRes: (proxyRes, request, response) => {
49 | // const setCookieHeader = proxyRes.headers['set-cookie'];
50 | //
51 | // //remove "Secure" option as such cookie will not be accepted when locally using HTTP
52 | // if (setCookieHeader && setCookieHeader.length > 0) {
53 | // let sessionCookie = setCookieHeader[0];
54 | //
55 | // if (/secure/i.test(sessionCookie)) {
56 | // sessionCookie = sessionCookie.replace(
57 | // /\s*Secure;/i,
58 | // ''
59 | // );
60 | // console.log(
61 | // `Rewriting set-cookie header to: ${sessionCookie}`
62 | // );
63 | // proxyRes.headers['set-cookie'] = [sessionCookie];
64 | // }
65 | // }
66 | // },
67 | // }
68 | // },
69 | };
70 | };
71 |
72 | const { configureCssLoaders } = require('./css.config.js');
73 |
74 | console.log(process.env.NODE_ENV);
75 |
76 | // Development module exports
77 | module.exports = merge(commonConfig(false), {
78 | output: {
79 | filename: '[name].bundle.js',
80 | publicPath: 'auto',
81 | clean: true,
82 | },
83 | mode: 'development',
84 | devtool: false,
85 | devServer: configureDevServer(),
86 | module: {
87 | rules: [...configureCssLoaders()],
88 | },
89 | plugins: [
90 | new webpack.DefinePlugin({
91 | WEBPACK_APP_VERSION: JSON.stringify(
92 | require('../package.json').version
93 | ),
94 | }),
95 | new HtmlWebpackPlugin({
96 | template: 'index.html',
97 | filename: 'index.html',
98 | inject: true,
99 | }),
100 | new webpack.HotModuleReplacementPlugin(),
101 | ],
102 | });
--------------------------------------------------------------------------------
/webpack/esbuild.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | configureESBuildLoader() {
3 | return {
4 | test: /\.js$/,
5 | exclude: [
6 | /(ext-)/,
7 | // \\ for Windows, / for macOS and Linux
8 | /node_modules[\\/]core-js/,
9 | /node_modules[\\/]webpack/,
10 | ],
11 | loader: 'esbuild-loader',
12 | options: {
13 | target: 'es2020',
14 | },
15 | };
16 | },
17 | };
--------------------------------------------------------------------------------
/webpack/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('autoprefixer'),
4 | ],
5 | };
--------------------------------------------------------------------------------
/webpack/prod-server.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const settings = require('./settings.js');
3 |
4 | module.exports = {
5 | mode: 'production',
6 | devServer: {
7 | host: settings.devServerConfig.host(),
8 | static: [
9 | // Simple example
10 | path.resolve(__dirname, settings.paths.dist.base),
11 | ],
12 | port: 9000,
13 | headers: {
14 | 'Access-Control-Allow-Origin': '*',
15 | },
16 | // Use it for proxy
17 | // proxy: {
18 | // '/path/api': {
19 | // target: '',
20 | // ws: true,
21 | // secure: false,
22 | // cookieDomainRewrite: 'localhost',
23 | // debug: true,
24 | // },
25 | // },
26 | },
27 | };
28 |
--------------------------------------------------------------------------------
/webpack/prod.js:
--------------------------------------------------------------------------------
1 | // webpack.prod.js - production builds
2 | const webpack = require('webpack');
3 | const git = require('git-rev-sync');
4 | const { merge } = require('webpack-merge');
5 | const moment = require('moment');
6 | const path = require('path');
7 |
8 | // webpack plugins
9 | //const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
10 | //const PurgecssPlugin = require('purgecss-webpack-plugin');
11 |
12 | const { CleanWebpackPlugin } = require('clean-webpack-plugin');
13 | const HtmlWebpackPlugin = require('html-webpack-plugin');
14 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
15 | const zopfli = require('@gfx/zopfli');
16 | //const CompressionPlugin = require('compression-webpack-plugin');
17 |
18 | // config files
19 | const commonConfig = require('./common.js');
20 | const settings = require('./settings.js');
21 |
22 | // Configure file banner
23 | const configureBanner = () => {
24 | return {
25 | banner: [
26 | '/*!',
27 | ' * @project ' + settings.name,
28 | ' * @name ' + '[base]',
29 | ' * @author ' + settings.copyright,
30 | ' * @build ' + moment().format('llll') + ' ET',
31 | ' * @release ' + git.long() + ' [' + git.branch() + ']',
32 | ' * @copyright Copyright (c) ' +
33 | moment().format('YYYY') +
34 | ' ' +
35 | settings.copyright,
36 | ' *',
37 | ' */',
38 | '',
39 | ].join('\n'),
40 | raw: true,
41 | include: [/bootstrap/],
42 | };
43 | };
44 |
45 | // Configure Compression webpack plugin
46 | const configureCompression = () => {
47 | return {
48 | filename: '[path].gz[query]',
49 | test: /\.(js|css|html|svg)$/,
50 | threshold: 10240,
51 | minRatio: 0.8,
52 | deleteOriginalAssets: false,
53 | compressionOptions: {
54 | numiterations: 15,
55 | level: 9,
56 | },
57 | algorithm(input, compressionOptions, callback) {
58 | return zopfli.gzip(input, compressionOptions, callback);
59 | },
60 | };
61 | };
62 |
63 | // Configure Clean webpack
64 | const configureCleanWebpack = () => {
65 | return {
66 | cleanOnceBeforeBuildPatterns: settings.paths.dist.clean,
67 | verbose: false,
68 | dry: false,
69 | };
70 | };
71 |
72 | const distPath = path.resolve(__dirname, settings.paths.dist.base);
73 |
74 | const { configureCssLoaders } = require('./css.config.js');
75 |
76 | // Production module exports
77 | module.exports = [
78 | merge(commonConfig(true), {
79 | mode: 'production',
80 | output: {
81 | path: distPath,
82 | filename: 'js/[name].[chunkhash].js',
83 | },
84 | module: {
85 | rules: [
86 | ...configureCssLoaders(),
87 | {
88 | test: /\.js$/,
89 | enforce: 'pre',
90 | exclude: /(node_modules|\.spec\.js)/,
91 | use: [
92 | {
93 | loader: 'webpack-strip-block',
94 | options: {
95 | start: '',
96 | end: '',
97 | },
98 | },
99 | ],
100 | },
101 | ],
102 | },
103 | plugins: [
104 | new webpack.BannerPlugin(configureBanner()),
105 | new webpack.DefinePlugin({
106 | WEBPACK_APP_VERSION: JSON.stringify(
107 | require('../package.json').version
108 | ),
109 | }),
110 | new CleanWebpackPlugin(configureCleanWebpack()),
111 | new MiniCssExtractPlugin({
112 | filename: 'resources/css/[name].[contenthash].css',
113 | experimentalUseImportModule: false,
114 | }),
115 | new HtmlWebpackPlugin({
116 | template: 'templates/index.html',
117 | filename: 'server-index.html',
118 | inject: true,
119 | }),
120 | new HtmlWebpackPlugin({
121 | template: 'index.html',
122 | filename: 'index.html',
123 | inject: true,
124 | }),
125 | // new webpack.SourceMapDevToolPlugin({
126 | // filename: '[file].map[query]',
127 | // exclude: [/(Ext)/],
128 | // }),
129 | //new CompressionPlugin(configureCompression()),
130 | // new BundleAnalyzerPlugin({
131 | // analyzerMode: 'static',
132 | // reportFilename: 'report-modern.html',
133 | // }),
134 | ],
135 | }),
136 | ];
137 |
--------------------------------------------------------------------------------
/webpack/settings.js:
--------------------------------------------------------------------------------
1 | // webpack.settings.js - webpack settings config
2 |
3 | require('dotenv').config();
4 |
5 | // Webpack settings exports
6 | // noinspection WebpackConfigHighlighting
7 | module.exports = {
8 | name: 'App',
9 | copyright: 'Add copyright here',
10 | paths: {
11 | resources: '../resources',
12 | src: '../src/',
13 | Ext: '../ext/build/',
14 | dist: {
15 | base: '../build/',
16 | clean: ['**/*'],
17 | },
18 | },
19 | urls: {
20 | publicPath: './',
21 | },
22 | entries: {
23 | bootstrap: 'bootstrap.js',
24 | },
25 | copyFiles: [
26 | {
27 | from: 'resources',
28 | to: 'resources',
29 | },
30 |
31 | // Add your assets. Example:
32 | // {
33 | // from: './node_modules/@arcgis/core/assets',
34 | // to: 'resources/@arcgis/assets',
35 | // },
36 | // {
37 | // from: './node_modules/monaco-editor/min/vs/base/browser/ui/codicons/codicon/',
38 | // to: 'resources/fonts/',
39 | // },
40 | // {
41 | // from: './node_modules/@pdftron/webviewer/public',
42 | // to: './libs/pdftron',
43 | // },
44 | ],
45 | devServerConfig: {
46 | host: () => process.env.DEVSERVER_HOST || 'localhost',
47 | poll: () => process.env.DEVSERVER_POLL || false,
48 | port: () => process.env.DEVSERVER_PORT || 8080,
49 | https: () => process.env.DEVSERVER_HTTPS || false,
50 | //remoteServerUrl: '',
51 | },
52 | };
53 |
--------------------------------------------------------------------------------