@asd14/vblocks 1.0.0 → 2.0.0
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/LICENSE +17 -25
- package/README.md +328 -93
- package/bin/vblocks.sh +107 -18
- package/package.json +46 -36
- package/src/postcss.config.js +7 -7
- package/src/types/client.d.ts +86 -0
- package/src/webpack.config.js +134 -26
- package/template/_common/_gitignore +3 -0
- package/template/_common/src/index.css +4 -0
- package/template/_common/src/index.html +11 -0
- package/template/_common/tsconfig.json +13 -0
- package/template/plain/package.json +23 -0
- package/template/plain/src/index.tsx +11 -0
- package/template/xyflow-playground/package.json +26 -0
- package/template/xyflow-playground/postcss.config.js +13 -0
- package/template/xyflow-playground/src/index.css +1 -0
- package/template/xyflow-playground/src/index.tsx +128 -0
- package/src/tailwind.config.js +0 -4
package/bin/vblocks.sh
CHANGED
|
@@ -8,20 +8,23 @@
|
|
|
8
8
|
#
|
|
9
9
|
# Available Commands:
|
|
10
10
|
# ---
|
|
11
|
+
# init: Creates a new project from the template
|
|
11
12
|
# build: Creates a production-ready build of your project
|
|
12
13
|
# serve: Starts a development server with live reload
|
|
13
|
-
# eject:
|
|
14
|
+
# eject: Prints a default config, save it in your project to customize it
|
|
14
15
|
#
|
|
15
16
|
# Usage Examples:
|
|
16
17
|
# ---
|
|
18
|
+
# npx vblocks init my-app # Create a project in ./my-app
|
|
19
|
+
# npx vblocks init my-flow --template xyflow-playground
|
|
17
20
|
# npx vblocks build # Build for production
|
|
18
21
|
# npx vblocks serve # Start dev server
|
|
19
|
-
# npx vblocks eject
|
|
22
|
+
# npx vblocks eject webpack > webpack.config.js # Customize a config
|
|
20
23
|
# =============================================================================
|
|
21
24
|
|
|
22
25
|
# Directory where this script is located and
|
|
23
26
|
# default configuration file paths (from this package).
|
|
24
|
-
export VB_DIR="$(dirname "$(dirname "$(
|
|
27
|
+
export VB_DIR="$(dirname "$(dirname "$(realpath "$0")")")"
|
|
25
28
|
|
|
26
29
|
# Project directory (where you run the command from) and
|
|
27
30
|
# project-specific config file paths.
|
|
@@ -37,7 +40,6 @@ select_config() {
|
|
|
37
40
|
# Determine config files location.
|
|
38
41
|
export VB_WEBPACK_CONFIG="$(select_config "webpack.config.js")"
|
|
39
42
|
export VB_POSTCSS_CONFIG="$(select_config "postcss.config.js")"
|
|
40
|
-
export VB_TAILWIND_CONFIG="$(select_config "tailwind.config.js")"
|
|
41
43
|
export VB_SWC_CONFIG="$(select_config ".swcrc")"
|
|
42
44
|
|
|
43
45
|
# Wrap webpack command with custom configs
|
|
@@ -62,15 +64,90 @@ run_webpack() {
|
|
|
62
64
|
"$@"
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
# Print the template names, one per line. A template is a folder in
|
|
68
|
+
# `template/` whose name has no `_` prefix
|
|
69
|
+
list_templates() {
|
|
70
|
+
for list_dir in "$VB_DIR"/template/*/; do
|
|
71
|
+
[ -d "$list_dir" ] || continue
|
|
72
|
+
list_name=$(basename "$list_dir")
|
|
73
|
+
|
|
74
|
+
case "$list_name" in
|
|
75
|
+
_*) ;;
|
|
76
|
+
*) printf '%s\n' "$list_name" ;;
|
|
77
|
+
esac
|
|
78
|
+
done
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Copy `template/_common`, then one template over it, into a directory that
|
|
82
|
+
# is missing or empty
|
|
83
|
+
init_project() {
|
|
84
|
+
init_usage="Usage: vblocks init <directory> [--template <name>]"
|
|
85
|
+
init_dir=""
|
|
86
|
+
init_template="plain"
|
|
87
|
+
|
|
88
|
+
while [ "$#" -gt 0 ]; do
|
|
89
|
+
case "$1" in
|
|
90
|
+
--template)
|
|
91
|
+
if [ "$#" -lt 2 ]; then
|
|
92
|
+
echo "$init_usage" >&2
|
|
93
|
+
exit 1
|
|
94
|
+
fi
|
|
95
|
+
init_template=$2
|
|
96
|
+
shift 2
|
|
97
|
+
;;
|
|
98
|
+
-*)
|
|
99
|
+
echo "$init_usage" >&2
|
|
100
|
+
exit 1
|
|
101
|
+
;;
|
|
102
|
+
*)
|
|
103
|
+
if [ -n "$init_dir" ]; then
|
|
104
|
+
echo "$init_usage" >&2
|
|
105
|
+
exit 1
|
|
106
|
+
fi
|
|
107
|
+
init_dir=$1
|
|
108
|
+
shift
|
|
109
|
+
;;
|
|
110
|
+
esac
|
|
111
|
+
done
|
|
69
112
|
|
|
70
|
-
if [
|
|
71
|
-
|
|
72
|
-
|
|
113
|
+
if [ -z "$init_dir" ]; then
|
|
114
|
+
echo "$init_usage" >&2
|
|
115
|
+
exit 1
|
|
73
116
|
fi
|
|
117
|
+
|
|
118
|
+
init_templates=$(list_templates)
|
|
119
|
+
if ! printf '%s\n' "$init_templates" | grep -qxF -- "$init_template"; then
|
|
120
|
+
printf 'Unknown template "%s". Available: %s\n' "$init_template" \
|
|
121
|
+
"$(printf '%s\n' "$init_templates" | paste -sd, - | sed 's/,/, /g')" >&2
|
|
122
|
+
exit 1
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
if [ -e "$init_dir" ] && [ -n "$(ls -A -- "$init_dir")" ]; then
|
|
126
|
+
printf '%s exists and is not empty\n' "$init_dir" >&2
|
|
127
|
+
exit 1
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
mkdir -p -- "$init_dir" || exit 1
|
|
131
|
+
cp -R -- "$VB_DIR/template/_common/." "$init_dir" || exit 1
|
|
132
|
+
cp -R -- "$VB_DIR/template/$init_template/." "$init_dir" || exit 1
|
|
133
|
+
|
|
134
|
+
# npm leaves every `.gitignore` out of the package, so the template ships
|
|
135
|
+
# `_gitignore`
|
|
136
|
+
mv -- "$init_dir/_gitignore" "$init_dir/.gitignore" || exit 1
|
|
137
|
+
|
|
138
|
+
init_name=$(basename "$(realpath "$init_dir")")
|
|
139
|
+
init_version=$(node -p "require('$VB_DIR/package.json').version")
|
|
140
|
+
|
|
141
|
+
(
|
|
142
|
+
cd -- "$init_dir" \
|
|
143
|
+
&& npm pkg set "name=$init_name" \
|
|
144
|
+
"devDependencies.@asd14/vblocks=^$init_version"
|
|
145
|
+
) || exit 1
|
|
146
|
+
|
|
147
|
+
printf 'Created %s. Next:\n\n' "$init_name"
|
|
148
|
+
printf ' cd %s\n' "$init_dir"
|
|
149
|
+
printf ' npm install\n'
|
|
150
|
+
printf ' npm start\n'
|
|
74
151
|
}
|
|
75
152
|
|
|
76
153
|
print_hero() {
|
|
@@ -103,7 +180,6 @@ print_hero() {
|
|
|
103
180
|
printf "│\n"
|
|
104
181
|
printf "│ %b\n" " Webpack: $(format_config_path "$VB_WEBPACK_CONFIG")"
|
|
105
182
|
printf "│ %b\n" " PostCSS: $(format_config_path "$VB_POSTCSS_CONFIG")"
|
|
106
|
-
printf "│ %b\n" " Tailwind: $(format_config_path "$VB_TAILWIND_CONFIG")"
|
|
107
183
|
printf "│ %b\n" " SWC: $(format_config_path "$VB_SWC_CONFIG")"
|
|
108
184
|
printf "╰──┤\n\n"
|
|
109
185
|
}
|
|
@@ -112,16 +188,23 @@ print_hero() {
|
|
|
112
188
|
# │ Main
|
|
113
189
|
# ╰────────
|
|
114
190
|
|
|
115
|
-
|
|
191
|
+
# stdout carries the config `eject` prints, keep the hero out of it
|
|
192
|
+
print_hero >&2
|
|
116
193
|
|
|
117
194
|
case "$1" in
|
|
195
|
+
init)
|
|
196
|
+
shift
|
|
197
|
+
init_project "$@"
|
|
198
|
+
;;
|
|
118
199
|
build)
|
|
119
200
|
shift
|
|
120
|
-
|
|
201
|
+
# Without a mode Webpack falls back to `production`, also for `serve`.
|
|
202
|
+
# A `--mode` in "$@" comes later, so it wins.
|
|
203
|
+
run_webpack build --mode production "$@"
|
|
121
204
|
;;
|
|
122
205
|
serve)
|
|
123
206
|
shift
|
|
124
|
-
run_webpack serve "$@"
|
|
207
|
+
run_webpack serve --mode development "$@"
|
|
125
208
|
;;
|
|
126
209
|
eject)
|
|
127
210
|
shift
|
|
@@ -129,16 +212,22 @@ case "$1" in
|
|
|
129
212
|
|
|
130
213
|
case "$file" in
|
|
131
214
|
webpack)
|
|
132
|
-
cat "$VB_DIR/webpack.config.js"
|
|
215
|
+
cat "$VB_DIR/src/webpack.config.js"
|
|
216
|
+
;;
|
|
217
|
+
postcss)
|
|
218
|
+
cat "$VB_DIR/src/postcss.config.js"
|
|
219
|
+
;;
|
|
220
|
+
swc)
|
|
221
|
+
cat "$VB_DIR/src/.swcrc"
|
|
133
222
|
;;
|
|
134
223
|
*)
|
|
135
|
-
echo "Unknown config file to eject. Available: webpack, postcss
|
|
224
|
+
echo "Unknown config file to eject. Available: webpack, postcss and swc" >&2
|
|
136
225
|
exit 1
|
|
137
226
|
;;
|
|
138
227
|
esac
|
|
139
228
|
;;
|
|
140
229
|
*)
|
|
141
|
-
echo "Usage: $0 {build|serve|eject}" >&2
|
|
230
|
+
echo "Usage: $0 {init|build|serve|eject}" >&2
|
|
142
231
|
exit 1
|
|
143
232
|
;;
|
|
144
233
|
esac
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@asd14/vblocks",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "2.0.0",
|
|
5
5
|
"description": " POSIX + Webpack, an unlikely story!",
|
|
6
|
+
"license": "MIT",
|
|
6
7
|
"sideEffects": false,
|
|
7
8
|
"publishConfig": {
|
|
8
9
|
"access": "public"
|
|
@@ -19,9 +20,16 @@
|
|
|
19
20
|
},
|
|
20
21
|
"files": [
|
|
21
22
|
"bin",
|
|
22
|
-
"src"
|
|
23
|
+
"src",
|
|
24
|
+
"template"
|
|
23
25
|
],
|
|
24
26
|
"type": "module",
|
|
27
|
+
"exports": {
|
|
28
|
+
"./client": {
|
|
29
|
+
"types": "./src/types/client.d.ts"
|
|
30
|
+
},
|
|
31
|
+
"./package.json": "./package.json"
|
|
32
|
+
},
|
|
25
33
|
"bin": {
|
|
26
34
|
"vblocks": "bin/vblocks.sh"
|
|
27
35
|
},
|
|
@@ -29,53 +37,55 @@
|
|
|
29
37
|
"----UTIL": "",
|
|
30
38
|
"prepare": "git config --local core.hooksPath .githook",
|
|
31
39
|
"format": "eslint --fix .",
|
|
32
|
-
"----CODE-
|
|
40
|
+
"----CODE-QA": "",
|
|
33
41
|
"lint": "eslint .",
|
|
34
|
-
"typecheck": "tsc --noEmit"
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"unit-test:template": "tools/verify-templates",
|
|
44
|
+
"unit-test": "npm run unit-test:template"
|
|
35
45
|
},
|
|
36
46
|
"dependencies": {
|
|
37
|
-
"@csstools/postcss-global-data": "^3.1.0",
|
|
38
47
|
"@mdx-js/loader": "^3.1.1",
|
|
39
|
-
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"postcss
|
|
49
|
-
"postcss-
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"swc-loader": "^0.2.
|
|
53
|
-
"
|
|
54
|
-
"webpack": "^5.
|
|
55
|
-
"webpack-
|
|
56
|
-
"webpack-
|
|
57
|
-
"webpack-dev-server": "^5.2.2"
|
|
48
|
+
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.3",
|
|
49
|
+
"@swc/helpers": "^0.5.23",
|
|
50
|
+
"browserslist": "^4.29.3",
|
|
51
|
+
"copy-webpack-plugin": "^14.0.0",
|
|
52
|
+
"css-loader": "^7.1.5",
|
|
53
|
+
"cssnano": "^9.6.0",
|
|
54
|
+
"dotenv-webpack": "^9.0.0",
|
|
55
|
+
"html-webpack-plugin": "^5.6.8",
|
|
56
|
+
"mini-css-extract-plugin": "^2.10.2",
|
|
57
|
+
"postcss": "^8.5.29",
|
|
58
|
+
"postcss-loader": "^8.2.1",
|
|
59
|
+
"postcss-preset-env": "^11.6.1",
|
|
60
|
+
"react-refresh": "^0.19.0",
|
|
61
|
+
"swc-loader": "^0.2.7",
|
|
62
|
+
"webpack": "^5.111.1",
|
|
63
|
+
"webpack-bundle-analyzer": "^5.4.0",
|
|
64
|
+
"webpack-cli": "^7.2.3",
|
|
65
|
+
"webpack-dev-server": "^6.0.0"
|
|
58
66
|
},
|
|
59
67
|
"devDependencies": {
|
|
60
|
-
"@asd14/eslint-config": "^
|
|
61
|
-
"@asd14/prettier-config": "^1.
|
|
62
|
-
"@asd14/
|
|
63
|
-
"@
|
|
64
|
-
"@commitlint/
|
|
65
|
-
"@
|
|
68
|
+
"@asd14/eslint-config": "^15.4.0",
|
|
69
|
+
"@asd14/prettier-config": "^1.5.0",
|
|
70
|
+
"@asd14/semantic-release-config": "^1.0.1",
|
|
71
|
+
"@asd14/ts-config": "^1.6.0",
|
|
72
|
+
"@commitlint/cli": "^21.2.3",
|
|
73
|
+
"@commitlint/config-conventional": "^21.2.3",
|
|
74
|
+
"@tailwindcss/postcss": "^4.3.3",
|
|
66
75
|
"@types/dotenv-webpack": "^7.0.8",
|
|
67
76
|
"@types/webpack-bundle-analyzer": "^4.7.0",
|
|
68
|
-
"
|
|
69
|
-
"eslint": "^9.
|
|
70
|
-
"prettier": "^3.
|
|
71
|
-
"semantic-release": "^
|
|
72
|
-
"
|
|
77
|
+
"@xyflow/react": "^12.12.0",
|
|
78
|
+
"eslint": "^9.39.5",
|
|
79
|
+
"prettier": "^3.9.10",
|
|
80
|
+
"semantic-release": "^25.0.9",
|
|
81
|
+
"tailwindcss": "^4.3.3",
|
|
82
|
+
"typescript": "^6.0.3"
|
|
73
83
|
},
|
|
74
84
|
"peerDependencies": {
|
|
75
85
|
"react": "^18 || ^19",
|
|
76
86
|
"react-dom": "^18 || ^19"
|
|
77
87
|
},
|
|
78
88
|
"engines": {
|
|
79
|
-
"node": ">=
|
|
89
|
+
"node": "^22.22.3 || ^24.15.0 || >=26.0"
|
|
80
90
|
}
|
|
81
91
|
}
|
package/src/postcss.config.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @param {{ mode: "production" | "development" | "none" }} api
|
|
3
|
+
*/
|
|
4
|
+
export default ({ mode }) => ({
|
|
2
5
|
plugins: {
|
|
3
6
|
"postcss-preset-env": {
|
|
4
7
|
stage: 2,
|
|
5
8
|
},
|
|
6
|
-
"
|
|
7
|
-
|
|
9
|
+
"cssnano": mode === "production" && {
|
|
10
|
+
preset: "default",
|
|
8
11
|
},
|
|
9
|
-
// "cssnano": {
|
|
10
|
-
// preset: "default",
|
|
11
|
-
// },
|
|
12
12
|
},
|
|
13
|
-
}
|
|
13
|
+
})
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the files `../webpack.config.js` teaches `import` to load.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* // tsconfig.json
|
|
6
|
+
* { "compilerOptions": { "types": ["@asd14/vblocks/client"] } }
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
declare module "*.module.css" {
|
|
10
|
+
/**
|
|
11
|
+
* Class names by their camelCase key, `.my-class` is `styles.myClass`.
|
|
12
|
+
*
|
|
13
|
+
* Typed `any` so `styles.myClass` passes `noPropertyAccessFromIndexSignature`.
|
|
14
|
+
*/
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
const classNames: any
|
|
17
|
+
|
|
18
|
+
export default classNames
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
declare module "*.css" {}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The URL of the image, a `data:` URL when the build inlines it.
|
|
25
|
+
*/
|
|
26
|
+
declare module "*.jpg" {
|
|
27
|
+
const url: string
|
|
28
|
+
|
|
29
|
+
export default url
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
declare module "*.gif" {
|
|
33
|
+
const url: string
|
|
34
|
+
|
|
35
|
+
export default url
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
declare module "*.png" {
|
|
39
|
+
const url: string
|
|
40
|
+
|
|
41
|
+
export default url
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
declare module "*.svg" {
|
|
45
|
+
const url: string
|
|
46
|
+
|
|
47
|
+
export default url
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The URL of the font, a `data:` URL in a single-file build.
|
|
52
|
+
*/
|
|
53
|
+
declare module "*.woff" {
|
|
54
|
+
const url: string
|
|
55
|
+
|
|
56
|
+
export default url
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
declare module "*.woff2" {
|
|
60
|
+
const url: string
|
|
61
|
+
|
|
62
|
+
export default url
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
declare module "*.ttf" {
|
|
66
|
+
const url: string
|
|
67
|
+
|
|
68
|
+
export default url
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
declare module "*.eot" {
|
|
72
|
+
const url: string
|
|
73
|
+
|
|
74
|
+
export default url
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The `.env` variables Webpack writes into the bundle. The browser has no
|
|
79
|
+
* other part of Node's `process`.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* const apiUrl = process.env["API_URL"]
|
|
83
|
+
*/
|
|
84
|
+
declare const process: {
|
|
85
|
+
env: Record<string, string | undefined>
|
|
86
|
+
}
|
package/src/webpack.config.js
CHANGED
|
@@ -7,6 +7,7 @@ import CopyPlugin from "copy-webpack-plugin"
|
|
|
7
7
|
import DotenvPlugin from "dotenv-webpack"
|
|
8
8
|
import HtmlWebPackPlugin from "html-webpack-plugin"
|
|
9
9
|
import MiniCssExtractPlugin from "mini-css-extract-plugin"
|
|
10
|
+
import webpack from "webpack"
|
|
10
11
|
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer"
|
|
11
12
|
|
|
12
13
|
const VB_SWC_CONFIG = process.env["VB_SWC_CONFIG"]
|
|
@@ -20,8 +21,10 @@ if (!VB_POSTCONFIG_PATH)
|
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Load, parse and provide targets defaults for SWC configuration
|
|
24
|
+
*
|
|
25
|
+
* @param {{ hasReactRefresh: boolean }} options
|
|
23
26
|
*/
|
|
24
|
-
const loadSwcConfig = () => {
|
|
27
|
+
const loadSwcConfig = ({ hasReactRefresh }) => {
|
|
25
28
|
const content = fs.readFileSync(VB_SWC_CONFIG, "utf8")
|
|
26
29
|
const config = /** @type {unknown} */ (JSON.parse(content))
|
|
27
30
|
const typedConfig = /** @type {import("@swc/core").Config} */ (config)
|
|
@@ -29,9 +32,22 @@ const loadSwcConfig = () => {
|
|
|
29
32
|
return {
|
|
30
33
|
...typedConfig,
|
|
31
34
|
env: {
|
|
32
|
-
targets: "> 0.25
|
|
35
|
+
targets: "> 0.25%, not dead",
|
|
33
36
|
...typedConfig.env,
|
|
34
37
|
},
|
|
38
|
+
jsc: {
|
|
39
|
+
...typedConfig.jsc,
|
|
40
|
+
transform: {
|
|
41
|
+
...typedConfig.jsc?.transform,
|
|
42
|
+
react: {
|
|
43
|
+
...typedConfig.jsc?.transform?.react,
|
|
44
|
+
// SWC marks components and their hooks, `ReactRefreshWebpackPlugin`
|
|
45
|
+
// loads the runtime that reads the marks. One without the other
|
|
46
|
+
// breaks the page
|
|
47
|
+
...(hasReactRefresh ? { development: true, refresh: true } : {}),
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
35
51
|
}
|
|
36
52
|
}
|
|
37
53
|
|
|
@@ -55,12 +71,83 @@ const createCssModuleConfig = isProduction => ({
|
|
|
55
71
|
localIdentName: isProduction ? "[hash:base64:8]" : "[local]__[hash:base64:5]",
|
|
56
72
|
})
|
|
57
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Replace every script, stylesheet and icon tag of the HTML with the content
|
|
76
|
+
* of the file it points at, then drop that file from the output.
|
|
77
|
+
*
|
|
78
|
+
* Lives in this file so an ejected config stays one file.
|
|
79
|
+
*
|
|
80
|
+
* @param {import("webpack").Compiler} compiler
|
|
81
|
+
*/
|
|
82
|
+
const inlineAssets = compiler => {
|
|
83
|
+
compiler.hooks.compilation.tap("inlineAssets", compilation => {
|
|
84
|
+
/** @param {string} name */
|
|
85
|
+
const takeAsset = name => {
|
|
86
|
+
const content = compilation.getAsset(name)?.source.source() ?? ""
|
|
87
|
+
|
|
88
|
+
compilation.deleteAsset(name)
|
|
89
|
+
|
|
90
|
+
return content
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** @param {import("html-webpack-plugin").HtmlTagObject} tag */
|
|
94
|
+
const inlineTag = tag => {
|
|
95
|
+
const { src, href, rel } = tag.attributes
|
|
96
|
+
|
|
97
|
+
if (tag.tagName === "script" && typeof src === "string")
|
|
98
|
+
return {
|
|
99
|
+
...tag,
|
|
100
|
+
attributes: {},
|
|
101
|
+
// The HTML parser reads `</script` and `<!--` inside a script, a
|
|
102
|
+
// `\x3C` means the same `<` to JavaScript and nothing to the parser
|
|
103
|
+
innerHTML: takeAsset(src)
|
|
104
|
+
.toString()
|
|
105
|
+
.replaceAll("</script", String.raw`\x3C/script`)
|
|
106
|
+
.replaceAll("<!--", String.raw`\x3C!--`),
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (rel === "stylesheet" && typeof href === "string")
|
|
110
|
+
return {
|
|
111
|
+
...tag,
|
|
112
|
+
tagName: "style",
|
|
113
|
+
voidTag: false,
|
|
114
|
+
attributes: {},
|
|
115
|
+
innerHTML: takeAsset(href)
|
|
116
|
+
.toString()
|
|
117
|
+
.replaceAll("</style", String.raw`<\/style`),
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (rel === "icon" && typeof href === "string")
|
|
121
|
+
return {
|
|
122
|
+
...tag,
|
|
123
|
+
attributes: {
|
|
124
|
+
rel,
|
|
125
|
+
// `bin/vblocks.sh` only ever passes a `favicon.svg`
|
|
126
|
+
href: `data:image/svg+xml;base64,${Buffer.from(takeAsset(href)).toString("base64")}`,
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return tag
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
HtmlWebPackPlugin.getHooks(compilation).alterAssetTagGroups.tap(
|
|
134
|
+
"inlineAssets",
|
|
135
|
+
data => ({
|
|
136
|
+
...data,
|
|
137
|
+
headTags: data.headTags.map(tag => inlineTag(tag)),
|
|
138
|
+
bodyTags: data.bodyTags.map(tag => inlineTag(tag)),
|
|
139
|
+
})
|
|
140
|
+
)
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
58
144
|
/**
|
|
59
145
|
* @typedef {Object} VBlocksEnvironment
|
|
60
146
|
*
|
|
61
147
|
* @property {string} entryDir - Target directory from where vblocks cmd was called
|
|
62
148
|
* @property {string} robotsPath - Path to robots.txt copied to the output directory
|
|
63
149
|
* @property {string} faviconPath - Path to favicon.ico copied to the output directory
|
|
150
|
+
* @property {boolean} [single] - Build one standalone index.html, every asset inlined
|
|
64
151
|
*/
|
|
65
152
|
|
|
66
153
|
/**
|
|
@@ -74,10 +161,12 @@ const createCssModuleConfig = isProduction => ({
|
|
|
74
161
|
* @param {VBlocksProps} props
|
|
75
162
|
* @returns {WebpackConfiguration}
|
|
76
163
|
*/
|
|
77
|
-
export default ({ entryDir, robotsPath, faviconPath }, { mode }) => {
|
|
164
|
+
export default ({ entryDir, robotsPath, faviconPath, single }, { mode }) => {
|
|
78
165
|
const isProduction = mode === "production"
|
|
79
166
|
const isServe = process.argv[2] == "serve"
|
|
80
|
-
const
|
|
167
|
+
const isSingle = Boolean(single) && !isServe
|
|
168
|
+
const hasReactRefresh = isServe && mode === "development"
|
|
169
|
+
const swcConfig = loadSwcConfig({ hasReactRefresh })
|
|
81
170
|
const commonPostCSSConfigs = createCssModuleConfig(isProduction)
|
|
82
171
|
|
|
83
172
|
const paths = {
|
|
@@ -102,7 +191,8 @@ export default ({ entryDir, robotsPath, faviconPath }, { mode }) => {
|
|
|
102
191
|
mode: mode,
|
|
103
192
|
|
|
104
193
|
output: {
|
|
105
|
-
|
|
194
|
+
// A standalone file opens from disk, where "/" is the filesystem root
|
|
195
|
+
publicPath: isSingle ? "" : "/",
|
|
106
196
|
path: paths.output,
|
|
107
197
|
// Cannot use 'contenthash' when hot reloading is enabled.
|
|
108
198
|
filename: isServe ? "js/[name].js" : "js/[name].[contenthash].js",
|
|
@@ -201,19 +291,27 @@ export default ({ entryDir, robotsPath, faviconPath }, { mode }) => {
|
|
|
201
291
|
},
|
|
202
292
|
{
|
|
203
293
|
test: /\.(jpg|gif|png|svg)$/,
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
294
|
+
...(isSingle
|
|
295
|
+
? { type: "asset/inline" }
|
|
296
|
+
: {
|
|
297
|
+
type: "asset",
|
|
298
|
+
parser: {
|
|
299
|
+
dataUrlCondition: {
|
|
300
|
+
maxSize: 10 * 1024,
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
}),
|
|
210
304
|
},
|
|
211
305
|
{
|
|
212
306
|
test: /\.(woff|woff2|ttf|eot)$/,
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
307
|
+
...(isSingle
|
|
308
|
+
? { type: "asset/inline" }
|
|
309
|
+
: {
|
|
310
|
+
type: "asset/resource",
|
|
311
|
+
generator: {
|
|
312
|
+
filename: "fonts/[name][ext]",
|
|
313
|
+
},
|
|
314
|
+
}),
|
|
217
315
|
},
|
|
218
316
|
],
|
|
219
317
|
},
|
|
@@ -243,6 +341,9 @@ export default ({ entryDir, robotsPath, faviconPath }, { mode }) => {
|
|
|
243
341
|
new HtmlWebPackPlugin({
|
|
244
342
|
template: paths.htmlTemplate,
|
|
245
343
|
favicon: paths.favicon,
|
|
344
|
+
// An inline script ignores `defer` and runs where it stands, so it
|
|
345
|
+
// must stand after the element the app renders into
|
|
346
|
+
inject: isSingle ? "body" : "head",
|
|
246
347
|
}),
|
|
247
348
|
|
|
248
349
|
new MiniCssExtractPlugin({
|
|
@@ -253,21 +354,27 @@ export default ({ entryDir, robotsPath, faviconPath }, { mode }) => {
|
|
|
253
354
|
path: paths.dotenv,
|
|
254
355
|
}),
|
|
255
356
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
357
|
+
!isSingle &&
|
|
358
|
+
new CopyPlugin({
|
|
359
|
+
patterns: [
|
|
360
|
+
{
|
|
361
|
+
from: paths.robots,
|
|
362
|
+
to: "robots.txt",
|
|
363
|
+
},
|
|
364
|
+
],
|
|
365
|
+
}),
|
|
366
|
+
|
|
367
|
+
// A dynamic `import()` would otherwise emit a second file
|
|
368
|
+
isSingle && new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
|
|
369
|
+
|
|
370
|
+
isSingle && inlineAssets,
|
|
264
371
|
|
|
265
372
|
isServe &&
|
|
266
373
|
new BundleAnalyzerPlugin({
|
|
267
374
|
openAnalyzer: false,
|
|
268
375
|
}),
|
|
269
376
|
|
|
270
|
-
|
|
377
|
+
hasReactRefresh && new ReactRefreshWebpackPlugin(),
|
|
271
378
|
].filter(Boolean),
|
|
272
379
|
|
|
273
380
|
resolve: {
|
|
@@ -285,7 +392,7 @@ export default ({ entryDir, robotsPath, faviconPath }, { mode }) => {
|
|
|
285
392
|
runtimeChunk: false,
|
|
286
393
|
|
|
287
394
|
// Extract node_module dependencies into a separate chunk for better caching
|
|
288
|
-
splitChunks: {
|
|
395
|
+
splitChunks: !isSingle && {
|
|
289
396
|
chunks: "all",
|
|
290
397
|
cacheGroups: {
|
|
291
398
|
react: {
|
|
@@ -305,7 +412,8 @@ export default ({ entryDir, robotsPath, faviconPath }, { mode }) => {
|
|
|
305
412
|
},
|
|
306
413
|
|
|
307
414
|
performance: {
|
|
308
|
-
|
|
415
|
+
// A standalone file is over the size limit by design
|
|
416
|
+
hints: isProduction && !isSingle ? "warning" : false,
|
|
309
417
|
},
|
|
310
418
|
}
|
|
311
419
|
}
|