@caweb/webpack 1.5.18 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/args.js ADDED
@@ -0,0 +1,46 @@
1
+ // flags can be passed via argv0
2
+ // we also add args from NODE_OPTIONS
3
+ let flags = [].concat(
4
+ processArgs(process.argv),
5
+ processArgs(process.argv0.split(' ')),
6
+ processArgs(process.env.NODE_OPTIONS ? process.env.NODE_OPTIONS.split(' ') : []),
7
+ )
8
+
9
+ // this function processes an array of arguments
10
+ // and returns an array of flags and values
11
+ function processArgs( arr ){
12
+ let tmp = [];
13
+
14
+ arr.filter(Boolean).map((o) => {
15
+ return o.replaceAll("'", '').split('=').forEach((e => tmp.push(e)))
16
+ });
17
+
18
+ return tmp
19
+ }
20
+
21
+ // function to add a flag
22
+ function addFlag(flag, value = null){
23
+ if( ! flagExists(flag) ){
24
+ flags.push(flag);
25
+ if( value ){
26
+ flags.push(value);
27
+ }
28
+ }
29
+ }
30
+
31
+ // check if a flag exists
32
+ function flagExists(flag){
33
+ return flags.includes(flag)
34
+ }
35
+
36
+ // get the value of a flag
37
+ function getArgVal(flag, defaultValue = null){
38
+ return flagExists(flag) ? flags[flags.indexOf(flag) + 1] : (defaultValue ?? false);
39
+ }
40
+
41
+ export {
42
+ flags,
43
+ flagExists,
44
+ addFlag,
45
+ getArgVal
46
+ };
package/lib/loader.js ADDED
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Various loader options for webpack handlebars loader.
3
+ * @see https://github.com/pcardune/handlebars-loader
4
+ */
5
+
6
+ import path from 'path';
7
+ import fs from 'fs';
8
+ import { fileURLToPath } from 'url';
9
+
10
+ const currentPath = path.dirname(fileURLToPath(import.meta.url));
11
+ const appPath = process.cwd();
12
+ const helperDirs = [...new Set([
13
+ getAllHelpers( path.join( currentPath, '..', 'helpers') ),// our loader helpers
14
+ fs.existsSync( path.join( appPath, 'helpers') ) ? // any custom helpers
15
+ [
16
+ path.join( appPath, 'helpers'),
17
+ ...getAllHelpers( path.join( appPath, 'helpers') ),
18
+ ] : []
19
+ ].flat())];
20
+
21
+
22
+ // function to resolve partials
23
+ const partialResolver = ( partial, callback ) => {
24
+ // fallback path to the @caweb/template package
25
+ const fallbackPath = path.join( currentPath, '..', '..', 'template' );
26
+
27
+ let partialDir = '';
28
+
29
+ // template parameter specific partials
30
+ switch ( partial ) {
31
+ /**
32
+ * Semantic elements are served from the /semantic/ directory
33
+ *
34
+ * - header
35
+ * - footer
36
+ *
37
+ * @link https://www.w3schools.com/html/html5_semantic_elements.asp
38
+ */
39
+ case 'branding':
40
+ case 'footer':
41
+ case 'header':
42
+ case 'mobileControls':
43
+ case 'navFooter':
44
+ case 'navHeader':
45
+ case 'utilityHeader':
46
+ partialDir = 'semantics';
47
+ break;
48
+
49
+ // content is served from the /content/ directory
50
+ case 'index':
51
+ case 'content':
52
+ partialDir = 'content';
53
+ break;
54
+
55
+ // components are served from the /components/ directory
56
+ case 'alert':
57
+ case 'card':
58
+ partialDir = `components/${partial}`;
59
+ break;
60
+
61
+ // forms are served from the /forms/ directory
62
+ case 'searchForm':
63
+ partialDir = 'forms';
64
+ break;
65
+
66
+ // tables are served from the /tables/ directory
67
+ case partial.includes('Table'):
68
+ partialDir = 'tables';
69
+ break;
70
+ }
71
+
72
+ // if the partial was mapped to specific directories under the @caweb/template
73
+ if( partialDir ){
74
+ // we remove the Form from the name
75
+ // we remove the Table from the name
76
+ // we change the partial name from camelCase to dash-case
77
+ partial = partial.replace(/(Form|Table)/, '').replace(/([A-Z])/g, '-$1').toLowerCase();
78
+
79
+ // if the partial exists in the appPath, use that first
80
+ // otherwise use the template path
81
+ if( fs.existsSync( path.join( appPath, partialDir, `/${partial}.html` ) ) ){
82
+ partial = path.join( appPath, partialDir, `/${partial}.html` );
83
+
84
+ // if the @caweb/template is installed we use that as the fallback
85
+ } else if( fs.existsSync( path.join( fallbackPath, partialDir, `/${partial}.html` ) ) ) {
86
+ partial = path.join( fallbackPath, partialDir, `/${partial}.html` );
87
+ }
88
+ }
89
+
90
+ callback(false, partial );
91
+
92
+ };
93
+
94
+ // get all helpers in a given path
95
+ function getAllHelpers( helpersPath ) {
96
+ return fs.readdirSync(
97
+ helpersPath,
98
+ {
99
+ withFileTypes: true ,
100
+ recursive: true
101
+ }
102
+ )
103
+ .filter( dirent => dirent.isDirectory() )
104
+ .map( dirent => path.join( dirent.parentPath, dirent.name ) );
105
+ }
106
+
107
+ export default {
108
+ // rootRelative: appPath,
109
+ partialResolver,
110
+ helperDirs,
111
+ precompileOptions: {
112
+ // prevent certain helpers from being precompiled
113
+ knownHelpersOnly: false,
114
+ preventIndent: true,
115
+ },
116
+ extensions: ['.html', '.handlebars', '.hbs', '' ],
117
+ };
package/lib/server.js ADDED
@@ -0,0 +1,134 @@
1
+ /**
2
+ * This file is used base configuration DevServer settings for Webpack
3
+ *
4
+ * @see https://webpack.js.org/configuration/dev-server/
5
+ */
6
+ /**
7
+ * External dependencies
8
+ */
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+
12
+ const appPath = process.cwd();
13
+
14
+ // default url
15
+ let server = 'http';
16
+ let host = 'localhost';
17
+ let port = 9000;
18
+
19
+ // list of project directories to watch for changes
20
+ // by default css/js files are watched by webpack
21
+ // these directories are also added to the static file serving with watch true
22
+ let watchFiles = fs.readdirSync(
23
+ path.join( appPath ),
24
+ {
25
+ withFileTypes: true,
26
+ }
27
+ )
28
+ .filter( (dirent) => {
29
+ // we exclude certain directories from being watched
30
+ let file = path.join( dirent.parentPath, dirent.name );
31
+ let excluded = file.startsWith( `${appPath}\\build`) ||
32
+ file.startsWith( `${appPath}\\src`) ||
33
+ file.includes( `\\.`) ||
34
+ file.includes( 'vendor' ) ||
35
+ file.includes( 'node_modules' );
36
+
37
+ return dirent.isDirectory() && ! excluded
38
+ })
39
+ .map( dirent => path.join( dirent.parentPath, dirent.name, '**', '*').replace(/\\/g, '/') )
40
+
41
+ // base dev server config
42
+ let devServer = {
43
+ server,
44
+ host,
45
+ port,
46
+ open: [ `${server}://${host}:${port}` ],
47
+
48
+ static: [
49
+ /**
50
+ * Static files are served from the following files in the following order
51
+ * we don't have to add the build directory since that is the output.path and proxied
52
+ *
53
+ * node_modules - Allows loading files from other npm packages
54
+ * media - Allows loading media files from the media directory
55
+ */
56
+ {
57
+ directory: path.join(appPath, 'node_modules'),
58
+ watch: false,
59
+ },
60
+ {
61
+ directory: path.join(appPath, 'media'),
62
+ watch: false,
63
+ },
64
+ ],
65
+
66
+ proxy:[
67
+ /**
68
+ * WordPress Proxy Configuration is deprecated
69
+ * @since 31.3.0
70
+ */
71
+ {
72
+ context: ['/build'],
73
+ target: `${server}://${host}:${port}`,
74
+ pathRewrite: {
75
+ '^/build': ''
76
+ },
77
+ logLevel: 'info'
78
+ },
79
+ /**
80
+ * We proxy the node_modules and media directory so they serve from the root
81
+ */
82
+ {
83
+ context: ['/node_modules'],
84
+ target: `${server}://${host}:${port}`,
85
+ pathRewrite: { '^/node_modules': '' },
86
+ },
87
+ {
88
+ context: ['/media'],
89
+ target: `${server}://${host}:${port}`,
90
+ pathRewrite: { '^/media': '' },
91
+ }
92
+ ],
93
+
94
+ liveReload: true,
95
+ hot: true,
96
+
97
+ // watchFiles
98
+ // we watch all folders in the app except build, node_modules and src
99
+ watchFiles: {
100
+ paths: watchFiles,
101
+ options: {
102
+ depth: 2,
103
+ },
104
+ },
105
+ }
106
+
107
+ // function to get the server config
108
+ const getServer = () => {
109
+ return devServer;
110
+ }
111
+
112
+ // function to add to the server config
113
+ const addToServer = ( key, value ) => {
114
+ devServer[ key ] = value;
115
+ }
116
+
117
+ // function to update target url settings
118
+ const updateTarget = ( url ) => {
119
+ // update which url to open
120
+ devServer.open = [ url ];
121
+
122
+ // update all the proxy targets
123
+ devServer.proxy = devServer.proxy.map( ( proxy ) => {
124
+ proxy.target = url;
125
+ return proxy;
126
+ } );
127
+
128
+ }
129
+
130
+ export {
131
+ getServer,
132
+ addToServer,
133
+ updateTarget
134
+ };
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Modified WordPress Scripts Webpack Configuration
3
+ *
4
+ * @package CAWebPublishing
5
+ * @link https://webpack.js.org/configuration/
6
+ */
7
+
8
+ /**
9
+ * External Dependencies
10
+ */
11
+ import baseConfig from '@wordpress/scripts/config/webpack.config.js';
12
+ import { getArgVal } from './args.js';
13
+
14
+ /**
15
+ * Internal dependencies
16
+ */
17
+
18
+ // Wordpress ignores the webpack --mode flag
19
+ // if the flag is passed we use that mode
20
+ // otherwise use whatever Wordpress is using
21
+ let mode = getArgVal('--mode') ? getArgVal('--mode') : baseConfig.mode;
22
+ let isProduction = mode === 'production';
23
+
24
+ // Update some of the default WordPress webpack rules.
25
+ baseConfig.module.rules.forEach((rule, i) => {
26
+ const r = new RegExp(rule.test).toString();
27
+
28
+ switch(r){
29
+ // WordPress adds a hash to asset file names we remove that hash.
30
+ case new RegExp(/\.(bmp|png|jpe?g|gif|webp)$/i).toString():
31
+ rule.generator.filename = 'images/[name][ext]';
32
+ break;
33
+ case new RegExp(/\.(woff|woff2|eot|ttf|otf)$/i).toString():
34
+ rule.generator.filename = 'fonts/[name][ext]';
35
+ break;
36
+ case new RegExp(/\.svg$/).toString():
37
+ // we don't want SVG to be asset/inline otherwise the resource may not be available.
38
+ // the asset should be an asset/resource we move them to the fonts folder.
39
+ if( 'asset/inline' === rule.type ){
40
+ rule.type = 'asset/resource';
41
+ rule.generator = { filename: 'fonts/[name][ext]' };
42
+
43
+ delete rule.issuer;
44
+ }
45
+ break;
46
+ // silence deprecation warnings from sass
47
+ case new RegExp(/\.(sc|sa)ss$/).toString():
48
+ rule.use[rule.use.length-1].options.sassOptions = {
49
+ silenceDeprecations: ['global-builtin', 'import', 'color-functions', 'if-function']
50
+ };
51
+ break;
52
+ case new RegExp(/\.m?(j|t)sx?$/).toString():
53
+ // @since @wordpress/scripts@30.20.0 babel-loader is used for js and ts files
54
+
55
+ // Added the Transform class properties syntax plugin to the babel-loader.
56
+ // @see https://babeljs.io/docs/en/babel-plugin-proposal-class-properties
57
+ rule.use[0].options.plugins.push('@babel/plugin-proposal-class-properties');
58
+
59
+ // we add thread-loader before the babel-loader
60
+ // Spawns multiple processes and split work between them. This makes faster build.
61
+ // @see https://webpack.js.org/loaders/thread-loader/
62
+ rule.use = [{
63
+ loader: 'thread-loader',
64
+ options: {
65
+ workers: -1,
66
+ },
67
+ }].concat(rule.use);
68
+
69
+ break;
70
+ }
71
+ });
72
+
73
+ // Update some of the default WordPress plugins.
74
+ baseConfig.plugins = baseConfig.plugins.map((plugin, i) => {
75
+ const pluginName = plugin.constructor.name;
76
+
77
+ switch(pluginName){
78
+ case 'MiniCssExtractPlugin':
79
+ // we change the default naming of the CSS files
80
+ plugin.options.filename = isProduction ? '[name].min.css' : '[name].css';
81
+ break;
82
+ case 'RtlCssPlugin':
83
+ // we disable the RTL CSS generation
84
+ plugin = false;
85
+ break;
86
+
87
+ }
88
+
89
+ return plugin;
90
+ }).filter( Boolean );
91
+
92
+ /**
93
+ * we remove the WordPress devServer declaration since we can only have 1 when exporting multiple configurations
94
+ *
95
+ * @see https://github.com/webpack/webpack-cli/issues/2408#issuecomment-793052542
96
+ */
97
+ delete baseConfig.devServer;
98
+
99
+ export default baseConfig;
package/package.json CHANGED
@@ -1,18 +1,20 @@
1
1
  {
2
2
  "name": "@caweb/webpack",
3
- "version": "1.5.18",
3
+ "version": "1.6.1",
4
4
  "description": "CAWebPublishing Webpack Configuration",
5
5
  "main": "webpack.config.js",
6
6
  "files": [
7
+ "helpers",
8
+ "lib",
9
+ "tests",
7
10
  "webpack.config.js",
8
- "tsconfig.json",
9
- "helpers"
11
+ "tsconfig.json"
10
12
  ],
11
13
  "type": "module",
12
14
  "scripts": {
13
15
  "webpack": "webpack",
14
- "serve": "webpack serve --config ./plugins/html/webpack.config.js",
15
- "config:test": "webpack configtest",
16
+ "test:config": "webpack configtest",
17
+ "test:serve": "set NODE_OPTIONS='--template ./patterns/default.html --search-template ./patterns/search.html' && webpack serve --config ./webpack.config.js ./tests/webpack.tests.js --merge",
16
18
  "test": "echo \\\"Error: run tests from root\\\" && exit 0"
17
19
  },
18
20
  "repository": {
@@ -37,26 +39,18 @@
37
39
  "@babel/plugin-proposal-class-properties": "^7.18.6",
38
40
  "@wordpress/scripts": "^31.3.0",
39
41
  "css-minimizer-webpack-plugin": "^7.0.4",
42
+ "deepmerge": "^4.3.1",
43
+ "handlebars": "^4.7.8",
40
44
  "handlebars-loader": "^1.7.3",
41
45
  "html-format": "^1.1.7",
42
- "html-webpack-link-type-plugin": "^1.1.1",
46
+ "html-webpack-plugin": "^5.6.6",
43
47
  "html-webpack-skip-assets-plugin": "^1.0.4",
44
- "mini-css-extract-plugin": "^2.10.0",
45
- "rtlcss-webpack-plugin": "^4.0.7",
46
48
  "thread-loader": "^4.0.4",
47
49
  "ts-loader": "^9.5.4",
48
50
  "webpack": "^5.104.1",
51
+ "webpack-cli": "^6.0.1",
49
52
  "webpack-dev-server": "^5.2.3",
53
+ "webpack-merge": "^6.0.1",
50
54
  "webpack-remove-empty-scripts": "^1.1.1"
51
- },
52
- "peerDependencies": {
53
- "@caweb/a11y-webpack-plugin": ">= 2.0.0",
54
- "@caweb/css-audit-webpack-plugin": ">= 2.0.0",
55
- "@caweb/html-webpack-plugin": ">= 2.0.0",
56
- "@caweb/jshint-webpack-plugin": ">= 2.0.0",
57
- "html-webpack-plugin": ">= 3.0.0 < 5.6.5"
58
- },
59
- "devDependencies": {
60
- "webpack-cli": "^6.0.1"
61
55
  }
62
56
  }
@@ -0,0 +1,54 @@
1
+ {
2
+ "_comment": "This is a sample caweb.json configuration file for CAWebPublishing projects.",
3
+ "site": {
4
+ "domain": "http://localhost:9000",
5
+ "logo": "./media/logo.png",
6
+ "favicon": "./media/favicon.ico",
7
+ "header": {
8
+ "utility": {
9
+ "links": []
10
+ },
11
+ "nav": [
12
+ {
13
+ "label": "CAWebPublishing",
14
+ "url": "https://caweb.cdt.ca.gov/",
15
+ "sub":[]
16
+ },
17
+ {
18
+ "label": "CDT",
19
+ "url": "https://cdt.ca.gov/",
20
+ "sub": [
21
+ {
22
+ "label": "Accessibility",
23
+ "url": "https://caweb.cdt.ca.gov/accessibility-2/",
24
+ "description": "Accessibility"
25
+ }
26
+ ]
27
+ }
28
+ ]
29
+ },
30
+ "footer":{
31
+ "nav": [
32
+ {
33
+ "label": "Accessibility",
34
+ "url": "https://caweb.cdt.ca.gov/accessibility-2/"
35
+ }
36
+ ]
37
+ },
38
+ "social": {
39
+ "github": "https://github.com/CAWebPublishing/"
40
+ },
41
+ "google": {
42
+ "search": "<Google Custom Search ID>"
43
+ },
44
+ "alerts": [
45
+ {
46
+ "icon": "info",
47
+ "header": "Dev Notice:",
48
+ "msg": "To see more CAWebPublishing projects, visit our Github Organization.",
49
+ "url": "https://github.com/CAWebPublishing/",
50
+ "text": "Visit Github"
51
+ }
52
+ ]
53
+ }
54
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * These are tests for the CAWebPublishing Template
3
+ */
4
+
5
+ /**
6
+ * External Dependencies
7
+ */
8
+ import path from 'path';
9
+ import fs from 'fs';
10
+ import { fileURLToPath } from 'url';
11
+ import HtmlWebpackPlugin from 'html-webpack-plugin';
12
+ import deepmerge from 'deepmerge';
13
+ import Handlebars from 'handlebars';
14
+
15
+ /**
16
+ * Internal Dependencies
17
+ */
18
+ import { getArgVal } from '../lib/args.js';
19
+
20
+ // this is the path to the current project directory
21
+ const appPath = process.cwd();
22
+
23
+ // this is the path to this current file
24
+ const currentPath = path.dirname(fileURLToPath(import.meta.url));
25
+
26
+ // we read the test data caweb.json file
27
+ let testCaweb = JSON.parse( fs.readFileSync( path.join(currentPath, 'caweb.json') ) );
28
+
29
+ // we read the app caweb.json file if it exists
30
+ let defaultCaweb = fs.existsSync( path.join(appPath, 'caweb.json') ) ?
31
+ JSON.parse(fs.readFileSync(path.join(appPath, 'caweb.json')))
32
+ : {};
33
+
34
+ // merge the two caweb.json files, with the project data taking precedence
35
+ let caweb = deepmerge( testCaweb, defaultCaweb );
36
+
37
+ let templatePath = path.join(appPath, 'node_modules', '@caweb', 'template');
38
+ let template = getArgVal( '--template', path.join(templatePath, 'patterns', 'default.html') );
39
+ let searchTemplate = getArgVal( '--search-template', path.join(templatePath, 'patterns', 'search.html') );
40
+
41
+ let scheme = getArgVal( '--scheme', 'oceanside' );
42
+ let favicon = caweb?.site?.favicon ?? null;
43
+
44
+ // // Additional pages directory
45
+ let basePageDir = path.join(appPath, 'content', 'pages');
46
+
47
+ let additionalPages = ! fs.existsSync( basePageDir ) ? [] :
48
+ fs.readdirSync( basePageDir, { withFileTypes: true, recursive: true } )
49
+ .filter( dirent => dirent.isFile() && (dirent.name.endsWith('.html') || dirent.name.endsWith('.handlebars')) )
50
+ .map( ( dirent ) => {
51
+
52
+ let fileTemplate = path.join( dirent.parentPath, dirent.name );
53
+
54
+ // replace .html, uppercase the first letter of each word
55
+ // this is to make sure the title is readable
56
+ // and not just a file name
57
+ let title = dirent.name.replace('.html', '').replace(/\b\w/g, c => c.toUpperCase());
58
+ let content = fs.readFileSync( fileTemplate, 'utf-8' );
59
+ let data = {
60
+ ...caweb.site,
61
+ scheme
62
+ };
63
+ let compiler = Handlebars.compile( content );
64
+ let compiledContent = compiler(data);
65
+
66
+ return new HtmlWebpackPlugin({
67
+ template,
68
+ filename: dirent.name,
69
+ title,
70
+ templateParameters: {
71
+ ...caweb.site, // we spread the site data found in the caweb.json file
72
+ scheme,
73
+ partial: compiledContent,
74
+ },
75
+
76
+ });
77
+ });
78
+
79
+ export default {
80
+ plugins: [
81
+ // this plugin generates the main landing page using the template found in patterns/index.html
82
+ new HtmlWebpackPlugin({
83
+ template,
84
+ favicon,
85
+ templateParameters: {
86
+ ...caweb.site, // we spread the site data found in the caweb.json file
87
+ scheme ,
88
+ }
89
+ }),
90
+
91
+ // this plugin generates Search Results page using the template found in patterns/search.html
92
+ caweb?.site?.google?.search ? new HtmlWebpackPlugin({
93
+ template: searchTemplate,
94
+ favicon,
95
+ filename: 'serp.html',
96
+ title: 'Search Results Page',
97
+ templateParameters: {
98
+ ...caweb.site, // we spread the site data found in the caweb.json file
99
+ scheme
100
+ },
101
+ }) : false,
102
+
103
+ // this plugin generates additional pages under content/pages directory using the template found in patterns/index.html
104
+ ...additionalPages
105
+ ].filter(Boolean)
106
+ };
package/webpack.config.js CHANGED
@@ -9,29 +9,33 @@
9
9
  /**
10
10
  * External Dependencies
11
11
  */
12
- import baseConfig from '@wordpress/scripts/config/webpack.config.js';
12
+ // import baseConfig from '@wordpress/scripts/config/webpack.config.js';
13
+ import baseConfig from './lib/webpack.wp.config.js';
13
14
  import fs from 'fs';
14
15
  import path from 'path';
15
16
  import { fileURLToPath } from 'url';
16
17
 
17
18
  // webpack plugins
18
- import MiniCSSExtractPlugin from 'mini-css-extract-plugin';
19
+ import { merge } from 'webpack-merge';
20
+ // import MiniCSSExtractPlugin from 'mini-css-extract-plugin';
19
21
  import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
20
- import RtlCssPlugin from 'rtlcss-webpack-plugin';
21
- import {HtmlWebpackSkipAssetsPlugin} from 'html-webpack-skip-assets-plugin';
22
- import {HtmlWebpackLinkTypePlugin} from 'html-webpack-link-type-plugin';
23
22
  import RemoveEmptyScriptsPlugin from 'webpack-remove-empty-scripts';
23
+ import {HtmlWebpackSkipAssetsPlugin} from 'html-webpack-skip-assets-plugin';
24
+ // import RtlCssPlugin from 'rtlcss-webpack-plugin';
25
+ // import {HtmlWebpackLinkTypePlugin} from 'html-webpack-link-type-plugin';
24
26
 
25
- import JSHintPlugin from '@caweb/jshint-webpack-plugin';
26
- import CSSAuditPlugin from '@caweb/css-audit-webpack-plugin';
27
- import A11yPlugin from '@caweb/a11y-webpack-plugin';
27
+ // import JSHintPlugin from '@caweb/jshint-webpack-plugin';
28
+ // import CSSAuditPlugin from '@caweb/css-audit-webpack-plugin';
29
+ // import A11yPlugin from '@caweb/a11y-webpack-plugin';
28
30
 
29
31
  /**
30
32
  * Internal dependencies
31
- */
32
- import CAWebHTMLPlugin from '@caweb/html-webpack-plugin';
33
- import { error } from 'console';
33
+ */
34
+ import { flags, flagExists, getArgVal, addFlag } from './lib/args.js';
35
+ import handlebarsLoaderOptions from './lib/loader.js';
36
+ import { addToServer, getServer, updateTarget } from './lib/server.js';
34
37
 
38
+ // determine the webpack command
35
39
  const webpackCommand = 'build' === process.argv[2] ? 'build' : 'serve' ;
36
40
 
37
41
  // this is the path to this current file
@@ -40,260 +44,104 @@ const currentPath = path.dirname(fileURLToPath(import.meta.url));
40
44
  // this is the path to the current project directory
41
45
  const appPath = process.cwd();
42
46
 
43
- // flags can be passed via argv0
44
- // we also add args from NODE_OPTIONS
45
- let flags = [].concat(
46
- processArgs(process.argv),
47
- processArgs(process.argv0.split(' ')),
48
- processArgs(process.env.NODE_OPTIONS ? process.env.NODE_OPTIONS.split(' ') : []),
49
- )
50
-
51
- const cawebJson = fs.existsSync( path.join(appPath, 'caweb.json') ) ?
47
+ // we read the caweb.json file if it exists
48
+ let caweb = fs.existsSync( path.join(appPath, 'caweb.json') ) ?
52
49
  JSON.parse(fs.readFileSync(path.join(appPath, 'caweb.json')))
53
50
  : {};
54
51
 
55
- // we use the caweb.json file to determine the site domain
56
- if( cawebJson?.site?.domain ){
57
- let siteDomain = new URL(cawebJson.site.domain);
58
-
59
- // only add the flags if the site domain is not localhost
60
- if( 'localhost' !== siteDomain.host ){
61
- // we add the site domain to the flags
62
- flags.push(
63
- `--host`, siteDomain.host,
64
- '--server-type', siteDomain.protocol.replace(':', ''),
65
- '--port', '' !== siteDomain.port ? cawebJson.site.port : 80, // default port is 80
66
- );
67
- }
68
- }
69
-
70
-
71
- function processArgs( arr ){
72
- let tmp = [];
73
-
74
- arr.filter(Boolean).map((o) => {
75
- return o.replaceAll("'", '').split('=').forEach((e => tmp.push(e)))
76
- });
77
-
78
- return tmp
79
- }
80
-
81
- function flagExists(flag){
82
- return flags.includes(flag)
83
- }
84
-
85
- function getArgVal(flag){
86
- return flagExists(flag) ? flags[flags.indexOf(flag) + 1] : false;
87
- }
88
-
89
- // Update some of the default WordPress webpack rules.
90
- baseConfig.module.rules.forEach((rule, i) => {
91
- const r = new RegExp(rule.test).toString();
92
-
93
- switch(r){
94
- // WordPress adds a hash to asset file names we remove that hash.
95
- case new RegExp(/\.(bmp|png|jpe?g|gif|webp)$/i).toString():
96
- rule.generator.filename = 'images/[name][ext]';
97
- break;
98
- case new RegExp(/\.(woff|woff2|eot|ttf|otf)$/i).toString():
99
- rule.generator.filename = 'fonts/[name][ext]';
100
- break;
101
- case new RegExp(/\.svg$/).toString():
102
- // we don't want SVG to be asset/inline otherwise the resource may not be available.
103
- // the asset should be an asset/resource we move them to the fonts folder.
104
- if( 'asset/inline' === rule.type ){
105
- rule.type = 'asset/resource';
106
- rule.generator = { filename: 'fonts/[name][ext]' };
107
-
108
- delete rule.issuer;
109
- }
110
- break;
111
- // silence deprecation warnings from sass
112
- case new RegExp(/\.(sc|sa)ss$/).toString():
113
- rule.use[rule.use.length-1].options.sassOptions = {
114
- silenceDeprecations: ['global-builtin', 'import', 'color-functions', 'if-function']
115
- };
116
- break;
117
- case new RegExp(/\.m?(j|t)sx?$/).toString():
118
- // @since @wordpress/scripts@30.20.0 babel-loader is used for js and ts files
119
-
120
- // Added the Transform class properties syntax plugin to the babel-loader.
121
- // @see https://babeljs.io/docs/en/babel-plugin-proposal-class-properties
122
- rule.use[0].options.plugins.push('@babel/plugin-proposal-class-properties');
123
-
124
- // we add thread-loader before the babel-loader
125
- // Spawns multiple processes and split work between them. This makes faster build.
126
- // @see https://webpack.js.org/loaders/thread-loader/
127
- rule.use = [{
128
- loader: 'thread-loader',
129
- options: {
130
- workers: -1,
131
- },
132
- }].concat(rule.use);
133
-
134
- break;
135
- }
136
- });
52
+ let mode = getArgVal('--mode') ? getArgVal('--mode') : baseConfig.mode;
53
+ let isProduction = mode === 'production';
54
+ let devServer = false;
137
55
 
138
56
  /**
139
- * we remove the WordPress devServer declaration since we can only have 1 when exporting multiple configurations
57
+ * DevServer is only added during 'serve' command
140
58
  *
141
- * @see https://github.com/webpack/webpack-cli/issues/2408#issuecomment-793052542
59
+ * @see https://webpack.js.org/configuration/dev-server/
142
60
  */
143
- delete baseConfig.devServer;
144
-
145
- let customTemplateHelpers = [];
146
-
147
- // we allow the user to pass custom template helpers
61
+ if( 'serve' === webpackCommand ){
62
+ // we use the caweb.json file to determine the site domain
63
+ if( caweb?.site?.domain ){
64
+ try {
65
+ let siteDomain = new URL(caweb.site.domain);
66
+
67
+ // only add the flags if the site domain is not localhost
68
+ if( 'localhost' !== siteDomain.hostname ){
69
+ addToServer( 'host', siteDomain.hostname );
70
+ addToServer( 'server', siteDomain.protocol.replace(':', '') );
71
+
72
+ // only add the port if it is specified
73
+ if( '' !== siteDomain.port ){
74
+ addToServer( 'port', siteDomain.port );
75
+ }
148
76
 
149
- if( fs.existsSync(path.join(appPath, 'helpers'), {withFileTypes: true} ) ) {
150
- // we add the helpers directory
151
- customTemplateHelpers = [ path.join(appPath, 'helpers') ];
152
-
153
- // we add any subdirectories
154
- fs.readdirSync(
155
- path.join(
156
- appPath, 'helpers'
157
- ),
158
- {
159
- withFileTypes: true,
160
- recursive: true
161
- }
162
- )
163
- .filter( Dirent => Dirent.isDirectory() )
164
- .map( Dirent => customTemplateHelpers.push( path.resolve(Dirent.parentPath, Dirent.name) ) )
77
+ updateTarget( siteDomain.href );
78
+ }
165
79
 
166
- }
80
+ } catch (e) {
81
+ console.error(`\x1b[31mInvalid URL in caweb.json site.domain: ${caweb.site.domain}\x1b[0m`);
82
+ console.error( '\x1b[31mEnsure the domain is a valid URL, e.g., https://example.com\x1b[0m' )
83
+ }
84
+
85
+ }
167
86
 
168
- // Wordpress ignores the webpack --mode flag
169
- // if the flag is passed we use that mode
170
- // otherwise use whatever Wordpress is using
171
- let mode = getArgVal('--mode') ? getArgVal('--mode') : baseConfig.mode;
87
+ // get the dev server config
88
+ devServer = getServer();
89
+ }
172
90
 
91
+ // main webpack configuration object
173
92
  let webpackConfig = {
174
- ...baseConfig,
175
93
  mode,
176
- name: 'uncompressed',
177
- target: 'web',
178
- // Turn off caching of generated modules and chunks.
179
- // @see https://webpack.js.org/configuration/cache/
180
- cache: false,
181
-
182
- stats: {
183
- errors: true,
184
- },
94
+ // target: 'web',
95
+ name: isProduction ? 'compressed' : 'uncompressed',
185
96
 
186
- // Determine where the created bundles will be outputted.
187
- // @see https://webpack.js.org/concepts/#output
188
- output: {
189
- ...baseConfig.output,
190
- clean: mode === 'production',
191
- pathinfo: false
97
+ /**
98
+ * Output Configuration
99
+ * @see https://webpack.js.org/configuration/output/
100
+ */
101
+ output: {
102
+ filename: isProduction ? '[name].min.js' : '[name].js',
103
+ chunkFilename: isProduction ? '[name].min.js?v=[chunkhash]' : '[name].js?v=[chunkhash]',
104
+ pathinfo: false,
105
+ clean: isProduction,
192
106
  },
193
-
194
- performance: {
195
- maxAssetSize: 500000,
196
- maxEntrypointSize: 500000
107
+
108
+ /**
109
+ * Resolve Configuration
110
+ *
111
+ * @see https://webpack.js.org/configuration/resolve/
112
+ */
113
+ resolve: {
114
+ extensions: ['.js', '.json'],
197
115
  },
198
116
 
199
- // Determine how modules are resolved.
200
- // @see https://webpack.js.org/configuration/resolve/
201
- resolve: {
202
- // Allows extension to be leave off when importing.
203
- // @see https://webpack.js.org/configuration/resolve/#resolveextensions
204
- extensions: ['.js', '.jsx', '.ts', '.tsx', '.json', '...'],
117
+ /**
118
+ * Optimization Configuration
119
+ * @see https://webpack.js.org/configuration/optimization/
120
+ */
121
+ optimization: {
122
+ minimize: isProduction,
123
+ minimizer: [
124
+ isProduction ? new CssMinimizerPlugin({test: /\.min\.css$/}) : false
125
+ ].filter(Boolean),
205
126
  },
206
127
 
207
128
  // This option determine how different types of module within the project will be treated.
208
129
  // @see https://webpack.js.org/configuration/module/
209
130
  module:{
210
- ...baseConfig.module,
211
131
  // This option sets up loaders for webpack configuration.
212
132
  // Loaders allow webpack to process various types because by default webpack only
213
133
  // understand JavaScript and JSON files.
214
134
  // @see https://webpack.js.org/concepts/#loaders
215
135
  rules: [
216
- ...baseConfig.module.rules,
217
136
  /**
218
137
  * Default template loader for html is lodash,
219
- * lets switch to handlebars
138
+ * lets switch to handlebars-loader
139
+ * @see https://github.com/pcardune/handlebars-loader
220
140
  */
221
141
  {
222
- test: /\.html$/,
142
+ test: /\.(html|handlebars|hbs)$/,
223
143
  loader: 'handlebars-loader',
224
- options:{
225
- rootRelative: process.cwd(),
226
- helperDirs: [
227
- path.resolve(currentPath, 'helpers', 'bootstrap'),
228
- path.resolve(currentPath, 'helpers', 'logic'),
229
- path.resolve(currentPath, 'helpers', 'object'),
230
- path.resolve(currentPath, 'helpers', 'string')
231
- ].concat( customTemplateHelpers ),
232
- partialResolver: function(partial, callback){
233
- /**
234
- * All template partials are loaded from the root directory
235
- * if the file doesn't exist we fallback to our template partials
236
- */
237
- let fallbackPath = path.join( currentPath, '..', 'template' );
238
- let partialDir = '';
239
-
240
- // template parameter specific partials
241
- switch( partial ){
242
- /**
243
- * Semantic elements are served from the /semantic/ directory
244
- *
245
- * - header
246
- * - footer
247
- *
248
- * @link https://www.w3schools.com/html/html5_semantic_elements.asp
249
- */
250
- case 'branding':
251
- case 'footer':
252
- case 'header':
253
- case 'mobileControls':
254
- case 'navFooter':
255
- case 'navHeader':
256
- case 'utilityHeader':
257
- partialDir = 'semantics';
258
- break;
259
-
260
- // content is served from the /content/ directory
261
- case 'index':
262
- case 'content':
263
- partialDir = 'content';
264
- break;
265
-
266
- // components are served from the /components/ directory
267
- case 'alert':
268
- case 'card':
269
- partialDir = `components/${partial}`;
270
- break;
271
-
272
- // forms are served from the /forms/ directory
273
- case 'searchForm':
274
- partialDir = 'forms';
275
- break;
276
-
277
- // tables are served from the /tables/ directory
278
- case partial.includes('Table'):
279
- partialDir = 'tables';
280
- break;
281
- }
282
-
283
- if( partialDir ){
284
- // we remove the Form from the name
285
- // we remove the Table from the name
286
- // we change the partial name from camelCase to dash-case
287
- partial = partial.replace(/(Form|Table)/, '').replace(/([A-Z])/g, '-$1').toLowerCase();
288
-
289
- partial = fs.existsSync( path.join( appPath, partialDir, `/${partial}.html` )) ?
290
- path.join( appPath, partialDir, `/${partial}.html` ) :
291
- path.join( fallbackPath, partialDir, `/${partial}.html` )
292
- }
293
-
294
- callback(false, partial );
295
- }
296
- }
144
+ options: handlebarsLoaderOptions
297
145
  },
298
146
  // Handle `.tsx` and `.ts` files.
299
147
  {
@@ -311,6 +159,43 @@ let webpackConfig = {
311
159
  }
312
160
  ]
313
161
  },
162
+
163
+ /**
164
+ * Devtool Configuration
165
+ * WordPress by default uses 'source-map' for devtool which affects build and rebuild speed.
166
+ * For development we switch to 'eval' which is much faster.
167
+ * For production we turn off devtool completely.
168
+ * @see https://webpack.js.org/configuration/devtool/#devtool
169
+ */
170
+ devtool: isProduction ? 'source-map' : 'eval',
171
+
172
+ /**
173
+ * Turn off caching of generated modules and chunks.
174
+ * @see https://webpack.js.org/configuration/cache/
175
+ */
176
+ cache: false,
177
+
178
+ /**
179
+ * Stats Configuration
180
+ * @see https://webpack.js.org/configuration/stats/
181
+ */
182
+ stats: {
183
+ errors: true,
184
+ errorDetails: true,
185
+ },
186
+
187
+
188
+ /**
189
+ * Performance Configuration
190
+ * Throw hints when asset size exceeds the specified limit for production.
191
+ *
192
+ * @see https://webpack.js.org/configuration/performance/
193
+ */
194
+ performance: {
195
+ maxAssetSize: 350000,
196
+ maxEntrypointSize: 500000,
197
+ hints: isProduction ? 'warning' : false,
198
+ },
314
199
 
315
200
  // WordPress already enqueues scripts and makes them available
316
201
  // in global scope so those scripts don't need to be included on the bundle. For webpack
@@ -329,190 +214,29 @@ let webpackConfig = {
329
214
  '@wordpress/hooks': ['vendor', 'wp', 'hooks'],
330
215
  '@wordpress/i18n': ['vendor', 'wp', 'i18n'],
331
216
 
332
- }
333
- };
334
-
335
- /**
336
- * Serve Only
337
- */
338
- if( 'serve' === webpackCommand ){
339
- let template = flags.includes('--template') ? getArgVal('--template') : 'default';
340
- let scheme = flags.includes('--scheme') ? getArgVal('--scheme') : 'oceanside';
217
+ },
341
218
 
342
- let host = flags.includes('--host') ? getArgVal('--host') : 'localhost';
343
- let port = flags.includes('--port') ? getArgVal('--port') : 9000;
344
- let server = flags.includes('--server-type') ? getArgVal('--server-type') : 'http';
219
+ plugins: [
220
+ // we remove empty scripts
221
+ new RemoveEmptyScriptsPlugin(),
345
222
 
346
- // Dev Server is added
347
- webpackConfig.devServer = {
348
- devMiddleware: {
349
- writeToDisk: true,
350
- },
351
- hot: true,
352
- compress: true,
353
- allowedHosts: 'auto',
354
- server,
355
- host,
356
- port,
357
- open: [ `${server}://${host}:${port}` ],
358
- static: [
359
- /**
360
- * Static files are served from the following files in the following order
361
- * we don't have to add the build directory since that is the output.path and proxied
362
- *
363
- * node_modules - Allows loading files from other npm packages
364
- */
365
- {
366
- directory: path.join(appPath, 'node_modules'),
367
- },
368
- /**
369
- * Static files are served from the following files in the following order
370
- * we don't have to add the build directory since that is the output.path and proxied
371
- *
372
- * node_modules - Allows loading files from other npm packages
373
- */
374
- {
375
- directory: path.join(appPath, 'media'),
376
- }
377
- ],
378
- proxy:[
379
- /**
380
- * WordPress Proxy Configuration is deprecated
381
- * @since 28.2.0
382
- */
383
- {
384
- context: ['/build'],
385
- target: `${server}://${host}:${port}`,
386
- pathRewrite: {
387
- '^/build': ''
388
- },
389
- logLevel: 'info'
390
- },
391
- /**
392
- * We proxy the node_modules and src so they serve from the root
393
- */
394
- {
395
- context: ['/node_modules'],
396
- target: `${server}://${host}:${port}`,
397
- pathRewrite: { '^/node_modules': '' },
398
- },
399
- /**
400
- * We proxy the node_modules and src so they serve from the root
401
- */
402
- {
403
- context: ['/media'],
404
- target: `${server}://${host}:${port}`,
405
- pathRewrite: { '^/media': '' },
406
- }
407
- ]
408
- }
409
-
410
- // Page Template and additional plugins
411
- webpackConfig.plugins.push(
412
- new CAWebHTMLPlugin({
413
- template,
414
- templateParameters: {
415
- scheme: 'false' !== scheme ? scheme : false
416
- },
223
+ // certain files can be skipped when serving
224
+ new HtmlWebpackSkipAssetsPlugin({
417
225
  skipAssets: [
418
- /.*-rtl.css/, // we skip the Right-to-Left Styles
419
- /css-audit.*/, // we skip the CSSAudit Files
420
- /a11y.*/, // we skip the A11y Files
421
- /jshint.*/, // we skip the JSHint Files
422
- ]
226
+ /.*-rtl.css/, // we skip the Right-to-Left Styles
227
+ /css-audit.*/, // we skip the CSSAudit Files
228
+ /a11y.*/, // we skip the A11y Files
229
+ /jshint.*/, // we skip the JSHint Files
230
+ ]
423
231
  }),
424
- new HtmlWebpackSkipAssetsPlugin(),
425
- new HtmlWebpackLinkTypePlugin(),
426
- ! flagExists('--no-jshint') ? new JSHintPlugin() : false,
427
- ! flagExists('--no-audit') ? new CSSAuditPlugin() : false,
428
- ! flagExists('--no-a11y') ? new A11yPlugin() : false
429
- )
430
-
431
- // we add the SERP (Search Engine Results Page)
432
- // if the caweb.json has a google search id
433
- if( cawebJson.site?.google?.search ){
434
- webpackConfig.plugins.push(
435
- new CAWebHTMLPlugin({
436
- template: 'search',
437
- templateParameters: {
438
- scheme: 'false' !== scheme ? scheme : false
439
- },
440
- filename: 'serp.html',
441
- title: 'Search Results Page',
442
- skipAssets: [
443
- /.*-rtl.css/, // we skip the Right-to-Left Styles
444
- /css-audit.*/, // we skip the CSSAudit Files
445
- /a11y.*/, // we skip the A11y Files
446
- /jshint.*/, // we skip the JSHint Files
447
- ]
448
- })
449
- )
450
- }
232
+ ],
451
233
 
452
- // we add any additional pages
453
- let basePageDir = path.join(appPath, 'content', 'pages');
454
- if( fs.existsSync(basePageDir, {withFileTypes: true} ) ) {
455
- fs.readdirSync(
456
- basePageDir,
457
- { withFileTypes: true, recursive: true }
458
- )
459
- .filter( Dirent => Dirent.isFile() && Dirent.name.endsWith('.html') )
460
- .map( Dirent => {
461
- let fileTemplate = path.join(Dirent.parentPath, Dirent.name);
462
- let p = fs.readFileSync( fileTemplate ).toString();
463
- let fileName = fileTemplate.replace(basePageDir, '');
464
-
465
- webpackConfig.plugins.push(
466
- new CAWebHTMLPlugin({
467
- template,
468
- filename: fileName,
469
- // replace .html, forward slashes with a space, uppercase the first letter of each word, remove Index
470
- // this is to make sure the title is readable
471
- // and not just a file name
472
- title: fileName.replace(/\.html$/, '').replace(/[\/\\]/g, ' ').replace(/\b\w/g, c => c.toUpperCase()).replace(' Index', ''),
473
- templateParameters: {
474
- bodyHtmlSnippet: p,
475
- },
476
- skipAssets: [
477
- /.*-rtl.css/, // we skip the Right-to-Left Styles
478
- /css-audit.*/, // we skip the CSSAudit Files
479
- /a11y.*/, // we skip the A11y Files
480
- /jshint.*/, // we skip the JSHint Files
481
- ]
482
- })
483
- )
484
- });
485
- }
486
- }
487
-
488
- /**
489
- * Production only
490
- */
491
- if( mode === 'production' ){
492
- // Config
493
- webpackConfig.name = 'compressed';
494
- webpackConfig.devtool = false;
495
-
496
- // Output
497
- webpackConfig.output.filename = '[name].min.js';
498
- webpackConfig.output.chunkFilename = '[name].min.js?v=[chunkhash]';
499
-
500
- // Plugins
501
- webpackConfig.plugins.push(
502
- new MiniCSSExtractPlugin( { filename: '[name].min.css' } ),
503
- new RtlCssPlugin( { filename: '[name]-rtl.min.css' } )
504
- )
505
-
506
- // Optimization
507
- webpackConfig.optimization.minimize = true;
508
- webpackConfig.optimization.minimizer.push(
509
- new CssMinimizerPlugin({test: /\.min\.css$/})
510
- )
511
- }
512
-
513
- // we remove empty scripts
514
- webpackConfig.plugins.push(
515
- new RemoveEmptyScriptsPlugin()
516
- );
234
+ /**
235
+ * DevServer is only added during 'serve' command
236
+ *
237
+ * @see https://webpack.js.org/configuration/dev-server/
238
+ */
239
+ devServer
240
+ };
517
241
 
518
- export default webpackConfig;
242
+ export default merge( baseConfig, webpackConfig );