balm-core 5.2.0 → 6.0.0-alpha.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/CHANGELOG.md +16 -0
- package/dist/index.cjs +2625 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +569 -0
- package/dist/index.d.ts +569 -0
- package/dist/index.js +2527 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -123
- package/src/balm.ts +108 -0
- package/src/bundler/esbuild.ts +47 -0
- package/src/bundler/index.ts +3 -0
- package/src/bundler/rollup.ts +45 -0
- package/src/bundler/webpack.ts +124 -0
- package/src/config/constants.ts +12 -0
- package/src/config/defaults.ts +209 -0
- package/src/config/index.ts +112 -0
- package/src/hooks/index.ts +224 -0
- package/src/index.ts +16 -0
- package/src/pipeline/file.ts +69 -0
- package/src/pipeline/index.ts +10 -0
- package/src/pipeline/pipeline.ts +135 -0
- package/src/pipeline/transforms/cache.ts +146 -0
- package/src/pipeline/transforms/htmlmin.ts +18 -0
- package/src/pipeline/transforms/less.ts +75 -0
- package/src/pipeline/transforms/postcss.ts +43 -0
- package/src/pipeline/transforms/rename.ts +37 -0
- package/src/pipeline/transforms/replace.ts +30 -0
- package/src/pipeline/transforms/sass.ts +106 -0
- package/src/pipeline/transforms/terser.ts +20 -0
- package/src/runner/dag.ts +77 -0
- package/src/runner/task.ts +38 -0
- package/src/shims.d.ts +5 -0
- package/src/tasks/cache.ts +72 -0
- package/src/tasks/clean.ts +21 -0
- package/src/tasks/copy.ts +36 -0
- package/src/tasks/ftp.ts +90 -0
- package/src/tasks/html.ts +232 -0
- package/src/tasks/index.ts +14 -0
- package/src/tasks/publish.ts +71 -0
- package/src/tasks/pwa.ts +99 -0
- package/src/tasks/remove.ts +24 -0
- package/src/tasks/replace.ts +34 -0
- package/src/tasks/script.ts +28 -0
- package/src/tasks/server.ts +183 -0
- package/src/tasks/static.ts +104 -0
- package/src/tasks/style.ts +57 -0
- package/src/tasks/zip.ts +65 -0
- package/src/types/index.ts +202 -0
- package/src/utilities/file.ts +120 -0
- package/src/utilities/logger.ts +63 -0
- package/src/utilities/utils.ts +46 -0
- package/src/utilities/workspace.ts +38 -0
- package/src/watcher/index.ts +100 -0
- package/tsconfig.json +8 -0
- package/tsup.config.ts +12 -0
- package/README.md +0 -196
- package/index.d.ts +0 -360
- package/lib/balm.js +0 -49
- package/lib/bootstrap/check_config.js +0 -43
- package/lib/bootstrap/index.js +0 -87
- package/lib/bundler/esbuild/build.js +0 -26
- package/lib/bundler/esbuild/index.js +0 -7
- package/lib/bundler/esbuild/options.js +0 -7
- package/lib/bundler/esbuild/transform.js +0 -36
- package/lib/bundler/rollup/build.js +0 -52
- package/lib/bundler/rollup/entry.js +0 -28
- package/lib/bundler/rollup/index.js +0 -20
- package/lib/bundler/rollup/output.js +0 -20
- package/lib/bundler/rollup/plugins.js +0 -31
- package/lib/bundler/webpack/config/common.js +0 -135
- package/lib/bundler/webpack/config/dev.js +0 -12
- package/lib/bundler/webpack/config/env.js +0 -67
- package/lib/bundler/webpack/config/index.js +0 -6
- package/lib/bundler/webpack/config/prod.js +0 -39
- package/lib/bundler/webpack/config/regex.js +0 -10
- package/lib/bundler/webpack/entry.js +0 -65
- package/lib/bundler/webpack/index.js +0 -32
- package/lib/bundler/webpack/loaders.js +0 -50
- package/lib/bundler/webpack/output.js +0 -41
- package/lib/bundler/webpack/rules/asset.js +0 -33
- package/lib/bundler/webpack/rules/babel.js +0 -19
- package/lib/bundler/webpack/rules/css.js +0 -109
- package/lib/bundler/webpack/rules/html.js +0 -15
- package/lib/bundler/webpack/rules/index.js +0 -10
- package/lib/config/assets.js +0 -30
- package/lib/config/constants.js +0 -14
- package/lib/config/env.js +0 -35
- package/lib/config/extras.js +0 -6
- package/lib/config/ftp.js +0 -6
- package/lib/config/globals.js +0 -43
- package/lib/config/html.js +0 -22
- package/lib/config/images.js +0 -4
- package/lib/config/index.js +0 -63
- package/lib/config/logs.js +0 -11
- package/lib/config/paths.js +0 -46
- package/lib/config/pwa.js +0 -24
- package/lib/config/roots.js +0 -9
- package/lib/config/scripts.js +0 -162
- package/lib/config/server.js +0 -84
- package/lib/config/spinner.js +0 -9
- package/lib/config/styles.js +0 -67
- package/lib/hello.js +0 -19
- package/lib/hooks/index.js +0 -94
- package/lib/hooks/maker.js +0 -59
- package/lib/index.js +0 -5
- package/lib/middlewares/history.js +0 -6
- package/lib/middlewares/index.js +0 -11
- package/lib/middlewares/proxy.js +0 -28
- package/lib/middlewares/webpack.js +0 -26
- package/lib/plugins/htmlmin.js +0 -39
- package/lib/plugins/imagemin.js +0 -95
- package/lib/plugins/index.js +0 -27
- package/lib/plugins/jsmin.js +0 -37
- package/lib/plugins/less.js +0 -97
- package/lib/plugins/plumber.js +0 -87
- package/lib/plugins/postcss-plugins.js +0 -13
- package/lib/plugins/postcss.js +0 -117
- package/lib/plugins/rename.js +0 -51
- package/lib/plugins/replace.js +0 -91
- package/lib/plugins/sass-legacy.js +0 -131
- package/lib/plugins/sass.js +0 -134
- package/lib/plugins/sftp.js +0 -288
- package/lib/tasks/default.js +0 -46
- package/lib/tasks/foundation/balm.js +0 -72
- package/lib/tasks/foundation/balm_style.js +0 -65
- package/lib/tasks/foundation/index.js +0 -10
- package/lib/tasks/index.js +0 -48
- package/lib/tasks/private/build.js +0 -18
- package/lib/tasks/private/cache.js +0 -31
- package/lib/tasks/private/clean.js +0 -69
- package/lib/tasks/private/end.js +0 -24
- package/lib/tasks/private/extra.js +0 -35
- package/lib/tasks/private/font.js +0 -15
- package/lib/tasks/private/image.js +0 -37
- package/lib/tasks/private/index.js +0 -34
- package/lib/tasks/private/lint.js +0 -20
- package/lib/tasks/private/media.js +0 -15
- package/lib/tasks/private/modernizr.js +0 -59
- package/lib/tasks/private/pwa-cache.js +0 -32
- package/lib/tasks/private/script.js +0 -9
- package/lib/tasks/private/sprite.js +0 -105
- package/lib/tasks/private/start.js +0 -16
- package/lib/tasks/private/style.js +0 -9
- package/lib/tasks/private/workbox-sw.js +0 -15
- package/lib/tasks/public/copy.js +0 -16
- package/lib/tasks/public/esbuild.js +0 -18
- package/lib/tasks/public/ftp.js +0 -39
- package/lib/tasks/public/html.js +0 -65
- package/lib/tasks/public/index.js +0 -38
- package/lib/tasks/public/jsmin.js +0 -23
- package/lib/tasks/public/less.js +0 -21
- package/lib/tasks/public/postcss.js +0 -16
- package/lib/tasks/public/publish.js +0 -51
- package/lib/tasks/public/pwa.js +0 -84
- package/lib/tasks/public/remove.js +0 -46
- package/lib/tasks/public/replace.js +0 -26
- package/lib/tasks/public/rollup.js +0 -17
- package/lib/tasks/public/sass.js +0 -27
- package/lib/tasks/public/server.js +0 -130
- package/lib/tasks/public/url.js +0 -35
- package/lib/tasks/public/version.js +0 -23
- package/lib/tasks/public/webpack.js +0 -39
- package/lib/tasks/public/zip.js +0 -19
- package/lib/utilities/address.js +0 -55
- package/lib/utilities/cache-core.js +0 -66
- package/lib/utilities/color.js +0 -27
- package/lib/utilities/compiling.js +0 -24
- package/lib/utilities/detect-port.js +0 -100
- package/lib/utilities/escape-string-regexp.js +0 -10
- package/lib/utilities/file.js +0 -70
- package/lib/utilities/index.js +0 -8
- package/lib/utilities/loading.js +0 -37
- package/lib/utilities/logger.js +0 -99
- package/lib/utilities/merge-stream.js +0 -38
- package/lib/utilities/namespace.js +0 -11
- package/lib/utilities/public-url.js +0 -37
- package/lib/utilities/replacestream.js +0 -106
- package/lib/utilities/typeof.js +0 -5
- package/lib/utilities/utils.js +0 -43
- package/tslib/balm.js +0 -44
- package/tslib/bootstrap/check_config.js +0 -36
- package/tslib/bootstrap/index.js +0 -82
- package/tslib/bundler/esbuild/build.js +0 -30
- package/tslib/bundler/esbuild/index.js +0 -11
- package/tslib/bundler/esbuild/options.js +0 -7
- package/tslib/bundler/esbuild/transform.js +0 -38
- package/tslib/bundler/rollup/build.js +0 -17
- package/tslib/bundler/rollup/entry.js +0 -24
- package/tslib/bundler/rollup/index.js +0 -21
- package/tslib/bundler/rollup/output.js +0 -23
- package/tslib/bundler/rollup/plugins.js +0 -27
- package/tslib/bundler/webpack/config/common.js +0 -130
- package/tslib/bundler/webpack/config/dev.js +0 -14
- package/tslib/bundler/webpack/config/env.js +0 -44
- package/tslib/bundler/webpack/config/index.js +0 -8
- package/tslib/bundler/webpack/config/prod.js +0 -43
- package/tslib/bundler/webpack/config/regex.js +0 -10
- package/tslib/bundler/webpack/entry.js +0 -68
- package/tslib/bundler/webpack/index.js +0 -30
- package/tslib/bundler/webpack/loaders.js +0 -51
- package/tslib/bundler/webpack/output.js +0 -41
- package/tslib/bundler/webpack/rules/asset.js +0 -33
- package/tslib/bundler/webpack/rules/babel.js +0 -16
- package/tslib/bundler/webpack/rules/css.js +0 -95
- package/tslib/bundler/webpack/rules/html.js +0 -15
- package/tslib/bundler/webpack/rules/index.js +0 -10
- package/tslib/config/assets.js +0 -24
- package/tslib/config/constants.js +0 -14
- package/tslib/config/env.js +0 -43
- package/tslib/config/extras.js +0 -6
- package/tslib/config/ftp.js +0 -6
- package/tslib/config/globals.js +0 -47
- package/tslib/config/html.js +0 -17
- package/tslib/config/images.js +0 -4
- package/tslib/config/index.js +0 -56
- package/tslib/config/logs.js +0 -6
- package/tslib/config/paths.js +0 -30
- package/tslib/config/pwa.js +0 -18
- package/tslib/config/roots.js +0 -8
- package/tslib/config/scripts.js +0 -93
- package/tslib/config/server.js +0 -33
- package/tslib/config/spinner.js +0 -10
- package/tslib/config/styles.js +0 -37
- package/tslib/hello.js +0 -19
- package/tslib/hooks/index.js +0 -75
- package/tslib/hooks/maker.js +0 -66
- package/tslib/index.js +0 -5
- package/tslib/middlewares/history.js +0 -12
- package/tslib/middlewares/index.js +0 -18
- package/tslib/middlewares/proxy.js +0 -27
- package/tslib/middlewares/webpack.js +0 -29
- package/tslib/plugins/htmlmin.js +0 -38
- package/tslib/plugins/imagemin.js +0 -97
- package/tslib/plugins/index.js +0 -27
- package/tslib/plugins/jsmin.js +0 -39
- package/tslib/plugins/less.js +0 -90
- package/tslib/plugins/plumber.js +0 -81
- package/tslib/plugins/postcss-plugins.js +0 -24
- package/tslib/plugins/postcss.js +0 -111
- package/tslib/plugins/rename.js +0 -51
- package/tslib/plugins/replace.js +0 -72
- package/tslib/plugins/sass-legacy.js +0 -95
- package/tslib/plugins/sass.js +0 -109
- package/tslib/plugins/sftp.js +0 -258
- package/tslib/tasks/default.js +0 -59
- package/tslib/tasks/foundation/balm.js +0 -75
- package/tslib/tasks/foundation/balm_style.js +0 -68
- package/tslib/tasks/foundation/index.js +0 -10
- package/tslib/tasks/index.js +0 -43
- package/tslib/tasks/private/build.js +0 -15
- package/tslib/tasks/private/cache.js +0 -48
- package/tslib/tasks/private/clean.js +0 -82
- package/tslib/tasks/private/end.js +0 -21
- package/tslib/tasks/private/extra.js +0 -34
- package/tslib/tasks/private/font.js +0 -12
- package/tslib/tasks/private/image.js +0 -42
- package/tslib/tasks/private/index.js +0 -34
- package/tslib/tasks/private/lint.js +0 -17
- package/tslib/tasks/private/media.js +0 -12
- package/tslib/tasks/private/modernizr.js +0 -54
- package/tslib/tasks/private/pwa-cache.js +0 -32
- package/tslib/tasks/private/script.js +0 -9
- package/tslib/tasks/private/sprite.js +0 -93
- package/tslib/tasks/private/start.js +0 -13
- package/tslib/tasks/private/style.js +0 -12
- package/tslib/tasks/private/workbox-sw.js +0 -15
- package/tslib/tasks/public/copy.js +0 -18
- package/tslib/tasks/public/esbuild.js +0 -18
- package/tslib/tasks/public/ftp.js +0 -39
- package/tslib/tasks/public/html.js +0 -75
- package/tslib/tasks/public/index.js +0 -38
- package/tslib/tasks/public/jsmin.js +0 -24
- package/tslib/tasks/public/less.js +0 -21
- package/tslib/tasks/public/postcss.js +0 -16
- package/tslib/tasks/public/publish.js +0 -50
- package/tslib/tasks/public/pwa.js +0 -87
- package/tslib/tasks/public/remove.js +0 -44
- package/tslib/tasks/public/replace.js +0 -27
- package/tslib/tasks/public/rollup.js +0 -17
- package/tslib/tasks/public/sass.js +0 -27
- package/tslib/tasks/public/server.js +0 -129
- package/tslib/tasks/public/url.js +0 -35
- package/tslib/tasks/public/version.js +0 -27
- package/tslib/tasks/public/webpack.js +0 -41
- package/tslib/tasks/public/zip.js +0 -19
- package/tslib/utilities/address.js +0 -48
- package/tslib/utilities/cache-core.js +0 -57
- package/tslib/utilities/color.js +0 -33
- package/tslib/utilities/compiling.js +0 -16
- package/tslib/utilities/detect-port.js +0 -91
- package/tslib/utilities/escape-string-regexp.js +0 -7
- package/tslib/utilities/file.js +0 -78
- package/tslib/utilities/index.js +0 -8
- package/tslib/utilities/loading.js +0 -25
- package/tslib/utilities/logger.js +0 -92
- package/tslib/utilities/merge-stream.js +0 -34
- package/tslib/utilities/namespace.js +0 -12
- package/tslib/utilities/public-url.js +0 -31
- package/tslib/utilities/replacestream.js +0 -112
- package/tslib/utilities/typeof.js +0 -8
- package/tslib/utilities/utils.js +0 -38
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fg from 'fast-glob';
|
|
3
|
+
import { BaseTask } from '../runner/task.js';
|
|
4
|
+
import { BalmConfig } from '../types/index.js';
|
|
5
|
+
import { Pipeline } from '../pipeline/pipeline.js';
|
|
6
|
+
import { transformHtmlmin } from '../pipeline/transforms/htmlmin.js';
|
|
7
|
+
import { transformReplace } from '../pipeline/transforms/replace.js';
|
|
8
|
+
import { PUBLIC_URL } from '../config/constants.js';
|
|
9
|
+
|
|
10
|
+
const LIVE_RELOAD_SCRIPT = `
|
|
11
|
+
<!-- BalmJS Live Reload -->
|
|
12
|
+
<script>
|
|
13
|
+
(() => {
|
|
14
|
+
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
15
|
+
const ws = new WebSocket(\`\${protocol}//\${location.host}/__balm_ws\`);
|
|
16
|
+
ws.onmessage = (e) => {
|
|
17
|
+
try {
|
|
18
|
+
const msg = JSON.parse(e.data);
|
|
19
|
+
if (msg.type === 'reload') {
|
|
20
|
+
console.log('[BalmJS] Reloading page...');
|
|
21
|
+
location.reload();
|
|
22
|
+
} else if (msg.type === 'reload-css') {
|
|
23
|
+
console.log('[BalmJS] Reloading CSS...');
|
|
24
|
+
const links = document.querySelectorAll('link[rel="stylesheet"]');
|
|
25
|
+
for (const link of links) {
|
|
26
|
+
const url = new URL(link.href);
|
|
27
|
+
url.searchParams.set('_balm_t', Date.now().toString());
|
|
28
|
+
link.href = url.href;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} catch {}
|
|
32
|
+
};
|
|
33
|
+
ws.onclose = () => {
|
|
34
|
+
setTimeout(() => location.reload(), 2000);
|
|
35
|
+
};
|
|
36
|
+
})();
|
|
37
|
+
</script>
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
export class HtmlTask extends BaseTask {
|
|
41
|
+
constructor() {
|
|
42
|
+
super({ name: 'html', description: 'Process HTML templates' });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async run(config: BalmConfig): Promise<void> {
|
|
46
|
+
const customTemplate = config.scripts.htmlPluginOptions?.template;
|
|
47
|
+
let pipeline: Pipeline;
|
|
48
|
+
|
|
49
|
+
if (customTemplate) {
|
|
50
|
+
const templatePath = path.isAbsolute(customTemplate)
|
|
51
|
+
? customTemplate
|
|
52
|
+
: path.join(config.workspace, customTemplate);
|
|
53
|
+
pipeline = Pipeline.from(templatePath, {
|
|
54
|
+
cwd: config.workspace,
|
|
55
|
+
base: path.dirname(templatePath)
|
|
56
|
+
});
|
|
57
|
+
} else {
|
|
58
|
+
const templateDir = config.scripts.injectHtml
|
|
59
|
+
? config.src.base
|
|
60
|
+
: config.src.html;
|
|
61
|
+
|
|
62
|
+
const pattern = path.join(templateDir, '*.html');
|
|
63
|
+
pipeline = Pipeline.from(pattern, {
|
|
64
|
+
cwd: config.workspace,
|
|
65
|
+
base: templateDir
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Replace %PUBLIC_URL% placeholder
|
|
70
|
+
const publicUrlReplacement = config.assets.virtualDir ? `/${config.assets.virtualDir}` : '';
|
|
71
|
+
pipeline.pipe(transformReplace(new RegExp(PUBLIC_URL, 'g'), publicUrlReplacement));
|
|
72
|
+
|
|
73
|
+
// Ensure output filename is index.html if custom template was used
|
|
74
|
+
if (customTemplate) {
|
|
75
|
+
pipeline.pipe(async (file) => {
|
|
76
|
+
file.basename = 'index.html';
|
|
77
|
+
return file;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// In production, map source paths to target paths in HTML
|
|
82
|
+
if (config.env.isProd) {
|
|
83
|
+
pipeline.pipe(async (file) => {
|
|
84
|
+
if (/\.html$/i.test(file.extname)) {
|
|
85
|
+
let content = file.toString();
|
|
86
|
+
if (
|
|
87
|
+
config.paths.source.css &&
|
|
88
|
+
config.paths.target.css &&
|
|
89
|
+
config.paths.source.css !== config.paths.target.css
|
|
90
|
+
) {
|
|
91
|
+
content = content.replaceAll(
|
|
92
|
+
`/${config.paths.source.css}/`,
|
|
93
|
+
`/${config.paths.target.css}/`
|
|
94
|
+
);
|
|
95
|
+
content = content.replaceAll(
|
|
96
|
+
`"${config.paths.source.css}/`,
|
|
97
|
+
`"${config.paths.target.css}/`
|
|
98
|
+
);
|
|
99
|
+
content = content.replaceAll(
|
|
100
|
+
`'${config.paths.source.css}/`,
|
|
101
|
+
`'${config.paths.target.css}/`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (
|
|
105
|
+
config.paths.source.js &&
|
|
106
|
+
config.paths.target.js &&
|
|
107
|
+
config.paths.source.js !== config.paths.target.js
|
|
108
|
+
) {
|
|
109
|
+
content = content.replaceAll(
|
|
110
|
+
`/${config.paths.source.js}/`,
|
|
111
|
+
`/${config.paths.target.js}/`
|
|
112
|
+
);
|
|
113
|
+
content = content.replaceAll(
|
|
114
|
+
`"${config.paths.source.js}/`,
|
|
115
|
+
`"${config.paths.target.js}/`
|
|
116
|
+
);
|
|
117
|
+
content = content.replaceAll(
|
|
118
|
+
`'${config.paths.source.js}/`,
|
|
119
|
+
`'${config.paths.target.js}/`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
if (
|
|
123
|
+
config.paths.source.img &&
|
|
124
|
+
config.paths.target.img &&
|
|
125
|
+
config.paths.source.img !== config.paths.target.img
|
|
126
|
+
) {
|
|
127
|
+
content = content.replaceAll(
|
|
128
|
+
`/${config.paths.source.img}/`,
|
|
129
|
+
`/${config.paths.target.img}/`
|
|
130
|
+
);
|
|
131
|
+
content = content.replaceAll(
|
|
132
|
+
`"${config.paths.source.img}/`,
|
|
133
|
+
`"${config.paths.target.img}/`
|
|
134
|
+
);
|
|
135
|
+
content = content.replaceAll(
|
|
136
|
+
`'${config.paths.source.img}/`,
|
|
137
|
+
`'${config.paths.target.img}/`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (
|
|
141
|
+
config.paths.source.font &&
|
|
142
|
+
config.paths.target.font &&
|
|
143
|
+
config.paths.source.font !== config.paths.target.font
|
|
144
|
+
) {
|
|
145
|
+
content = content.replaceAll(
|
|
146
|
+
`/${config.paths.source.font}/`,
|
|
147
|
+
`/${config.paths.target.font}/`
|
|
148
|
+
);
|
|
149
|
+
content = content.replaceAll(
|
|
150
|
+
`"${config.paths.source.font}/`,
|
|
151
|
+
`"${config.paths.target.font}/`
|
|
152
|
+
);
|
|
153
|
+
content = content.replaceAll(
|
|
154
|
+
`'${config.paths.source.font}/`,
|
|
155
|
+
`'${config.paths.target.font}/`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
file.contents = Buffer.from(content);
|
|
159
|
+
}
|
|
160
|
+
return file;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Auto-inject script tags if injectHtml is enabled
|
|
165
|
+
if (config.scripts.injectHtml) {
|
|
166
|
+
pipeline.pipe(async (file) => {
|
|
167
|
+
if (/\.html$/i.test(file.extname)) {
|
|
168
|
+
let content = file.toString();
|
|
169
|
+
const entries =
|
|
170
|
+
typeof config.scripts.entry === 'object' && !Array.isArray(config.scripts.entry)
|
|
171
|
+
? Object.keys(config.scripts.entry)
|
|
172
|
+
: ['main'];
|
|
173
|
+
|
|
174
|
+
const jsDir = config.env.isProd ? config.paths.target.js : config.paths.tmp.js;
|
|
175
|
+
const jsPathPrefix = config.assets.virtualDir ? `/${config.assets.virtualDir}/${jsDir}` : `/${jsDir}`;
|
|
176
|
+
const destJsFolder = config.dest.js;
|
|
177
|
+
|
|
178
|
+
const scriptTags = entries
|
|
179
|
+
.filter(
|
|
180
|
+
(entryName) =>
|
|
181
|
+
!new RegExp(`<script[^>]+src=["'][^"']*${entryName}[^"']*\\.js["']`, 'i').test(
|
|
182
|
+
content
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
.map((entryName) => {
|
|
186
|
+
let actualFilename = `${entryName}.js`;
|
|
187
|
+
try {
|
|
188
|
+
const matched = fg.sync(`${entryName}.*.js`, { cwd: destJsFolder });
|
|
189
|
+
if (matched.length) {
|
|
190
|
+
const valid = matched.find((f) => !f.endsWith('.map') && !f.endsWith('.txt'));
|
|
191
|
+
if (valid) {
|
|
192
|
+
actualFilename = valid;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} catch {}
|
|
196
|
+
return `<script defer src="${jsPathPrefix}/${actualFilename}"></script>`;
|
|
197
|
+
})
|
|
198
|
+
.join('\n');
|
|
199
|
+
|
|
200
|
+
if (scriptTags) {
|
|
201
|
+
if (content.includes('</body>')) {
|
|
202
|
+
content = content.replace('</body>', `${scriptTags}\n</body>`);
|
|
203
|
+
} else {
|
|
204
|
+
content += `\n${scriptTags}`;
|
|
205
|
+
}
|
|
206
|
+
file.contents = Buffer.from(content);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return file;
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (config.env.isProd) {
|
|
214
|
+
pipeline.pipe(transformHtmlmin(config.html.options));
|
|
215
|
+
} else {
|
|
216
|
+
pipeline.pipe(async (file) => {
|
|
217
|
+
if (/\.html$/i.test(file.extname)) {
|
|
218
|
+
let content = file.toString();
|
|
219
|
+
if (content.includes('</body>')) {
|
|
220
|
+
content = content.replace('</body>', `${LIVE_RELOAD_SCRIPT}\n</body>`);
|
|
221
|
+
} else {
|
|
222
|
+
content += `\n${LIVE_RELOAD_SCRIPT}`;
|
|
223
|
+
}
|
|
224
|
+
file.contents = Buffer.from(content);
|
|
225
|
+
}
|
|
226
|
+
return file;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
await pipeline.dest(config.dest.html);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from './clean.js';
|
|
2
|
+
export * from './style.js';
|
|
3
|
+
export * from './script.js';
|
|
4
|
+
export * from './html.js';
|
|
5
|
+
export * from './copy.js';
|
|
6
|
+
export * from './remove.js';
|
|
7
|
+
export * from './replace.js';
|
|
8
|
+
export * from './cache.js';
|
|
9
|
+
export * from './publish.js';
|
|
10
|
+
export * from './pwa.js';
|
|
11
|
+
export * from './zip.js';
|
|
12
|
+
export * from './ftp.js';
|
|
13
|
+
export * from './server.js';
|
|
14
|
+
export * from './static.js';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { BaseTask } from '../runner/task.js';
|
|
3
|
+
import { BalmConfig, TemplateOption, RenameOptions } from '../types/index.js';
|
|
4
|
+
import { Pipeline } from '../pipeline/pipeline.js';
|
|
5
|
+
import { transformRename } from '../pipeline/transforms/rename.js';
|
|
6
|
+
import { logger } from '../utilities/logger.js';
|
|
7
|
+
|
|
8
|
+
export interface PublishTaskOptions {
|
|
9
|
+
input?: string | TemplateOption[];
|
|
10
|
+
output?: string;
|
|
11
|
+
renameOptions?: string | Function | RenameOptions;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class PublishTask extends BaseTask {
|
|
15
|
+
private options: PublishTaskOptions;
|
|
16
|
+
|
|
17
|
+
constructor(options: PublishTaskOptions = {}) {
|
|
18
|
+
super({ name: 'publish', description: 'Publish assets or templates' });
|
|
19
|
+
this.options = options;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async run(config: BalmConfig): Promise<void> {
|
|
23
|
+
if (!config.env.isProd) {
|
|
24
|
+
logger.warn('publish', '`mix.publish()` is only supported in production mode');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { input, output, renameOptions } = this.options;
|
|
29
|
+
|
|
30
|
+
if (Array.isArray(input)) {
|
|
31
|
+
for (const item of input) {
|
|
32
|
+
await this.publishItem(config, item.input, item.output, item.renameOptions);
|
|
33
|
+
}
|
|
34
|
+
} else if (typeof input === 'string' && output) {
|
|
35
|
+
await this.publishItem(config, input, output, renameOptions);
|
|
36
|
+
} else {
|
|
37
|
+
// Default: publish all static assets from dest.base to assets.static
|
|
38
|
+
const srcDir = config.dest.base;
|
|
39
|
+
const targetDir = config.assets.static || path.join(config.assets.root, config.assets.mainDir);
|
|
40
|
+
|
|
41
|
+
const pipeline = Pipeline.from(['**/*'], {
|
|
42
|
+
cwd: srcDir,
|
|
43
|
+
base: srcDir
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await pipeline.dest(path.resolve(config.workspace, targetDir));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private async publishItem(
|
|
51
|
+
config: BalmConfig,
|
|
52
|
+
input: string,
|
|
53
|
+
output: string,
|
|
54
|
+
rename?: string | Function | RenameOptions
|
|
55
|
+
): Promise<void> {
|
|
56
|
+
const srcPath = path.isAbsolute(input) ? input : path.join(config.dest.base, input);
|
|
57
|
+
const destDir = path.isAbsolute(output)
|
|
58
|
+
? output
|
|
59
|
+
: path.join(config.workspace, config.assets.root, output);
|
|
60
|
+
|
|
61
|
+
const pipeline = Pipeline.from(srcPath, {
|
|
62
|
+
cwd: config.dest.base
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (rename) {
|
|
66
|
+
pipeline.pipe(transformRename(rename));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
await pipeline.dest(destDir);
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/tasks/pwa.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { generateSW, injectManifest } from 'workbox-build';
|
|
6
|
+
import { BaseTask } from '../runner/task.js';
|
|
7
|
+
import { BalmConfig } from '../types/index.js';
|
|
8
|
+
import { file as fsUtil } from '../utilities/file.js';
|
|
9
|
+
import { logger } from '../utilities/logger.js';
|
|
10
|
+
|
|
11
|
+
export interface PwaTaskOptions {
|
|
12
|
+
mode?: 'generateSW' | 'injectManifest';
|
|
13
|
+
options?: Record<string, any>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class PwaTask extends BaseTask {
|
|
17
|
+
private options: PwaTaskOptions;
|
|
18
|
+
|
|
19
|
+
constructor(options: PwaTaskOptions = {}) {
|
|
20
|
+
super({ name: 'pwa', description: 'Generate Progressive Web App Service Worker' });
|
|
21
|
+
this.options = options;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async run(config: BalmConfig): Promise<void> {
|
|
25
|
+
const globDirectory = config.dest.base;
|
|
26
|
+
const swDest = path.join(globDirectory, config.pwa.swDestFilename);
|
|
27
|
+
const swSrc = path.join(config.src.base, config.pwa.swSrcFilename);
|
|
28
|
+
|
|
29
|
+
// 1. Copy workbox-sw.js and workbox-sw.js.map to dest
|
|
30
|
+
const require = createRequire(import.meta.url);
|
|
31
|
+
const candidatePaths: string[] = [
|
|
32
|
+
path.join(config.workspace, 'node_modules/workbox-sw/build/workbox-sw.js'),
|
|
33
|
+
path.join(config.workspace, 'node_modules/workbox-sw/controllers/WorkboxSW.mjs')
|
|
34
|
+
];
|
|
35
|
+
try {
|
|
36
|
+
const resolved = require.resolve('workbox-sw/build/workbox-sw.js');
|
|
37
|
+
candidatePaths.unshift(resolved);
|
|
38
|
+
} catch {}
|
|
39
|
+
try {
|
|
40
|
+
const resolvedPkg = require.resolve('workbox-sw');
|
|
41
|
+
candidatePaths.push(path.join(path.dirname(resolvedPkg), 'build/workbox-sw.js'));
|
|
42
|
+
} catch {}
|
|
43
|
+
|
|
44
|
+
for (const p of candidatePaths) {
|
|
45
|
+
if (existsSync(p)) {
|
|
46
|
+
const destFile = path.join(globDirectory, 'workbox-sw.js');
|
|
47
|
+
await fs.copyFile(p, destFile);
|
|
48
|
+
const mapFile = `${p}.map`;
|
|
49
|
+
if (existsSync(mapFile)) {
|
|
50
|
+
await fs.copyFile(mapFile, path.join(globDirectory, 'workbox-sw.js.map'));
|
|
51
|
+
}
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 2. Generate or inject manifest
|
|
57
|
+
const mode = this.options.mode || config.pwa.mode || 'generateSW';
|
|
58
|
+
const mergedOptions: any = {
|
|
59
|
+
globDirectory,
|
|
60
|
+
swDest,
|
|
61
|
+
...config.pwa.options,
|
|
62
|
+
...this.options.options
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
if (mode === 'injectManifest') {
|
|
66
|
+
mergedOptions.swSrc = mergedOptions.swSrc || swSrc;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
if (mode === 'generateSW') {
|
|
71
|
+
const { count, size } = await generateSW(mergedOptions);
|
|
72
|
+
logger.info('pwa', `Generated '${swDest}', precaching ${count} files (${size} bytes)`);
|
|
73
|
+
} else {
|
|
74
|
+
const { count, size } = await injectManifest(mergedOptions);
|
|
75
|
+
logger.info('pwa', `Injected manifest into '${swDest}', precaching ${count} files (${size} bytes)`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 3. Replace version placeholder in swDest
|
|
79
|
+
if (existsSync(swDest) && config.pwa.version) {
|
|
80
|
+
let swContent = await fs.readFile(swDest, 'utf-8');
|
|
81
|
+
swContent = swContent
|
|
82
|
+
.replaceAll('{{ version }}', config.pwa.version)
|
|
83
|
+
.replaceAll('{{version}}', config.pwa.version);
|
|
84
|
+
await fs.writeFile(swDest, swContent, 'utf-8');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 4. Ensure both service-worker.js and sw.js exist in dest for compatibility
|
|
88
|
+
const swJsDest = path.join(globDirectory, 'sw.js');
|
|
89
|
+
const serviceWorkerJsDest = path.join(globDirectory, 'service-worker.js');
|
|
90
|
+
if (existsSync(serviceWorkerJsDest) && !existsSync(swJsDest)) {
|
|
91
|
+
await fs.copyFile(serviceWorkerJsDest, swJsDest);
|
|
92
|
+
} else if (existsSync(swJsDest) && !existsSync(serviceWorkerJsDest)) {
|
|
93
|
+
await fs.copyFile(swJsDest, serviceWorkerJsDest);
|
|
94
|
+
}
|
|
95
|
+
} catch (err: any) {
|
|
96
|
+
logger.warn('pwa', `Service worker generation failed: ${err.message || err}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { BaseTask } from '../runner/task.js';
|
|
3
|
+
import { BalmConfig } from '../types/index.js';
|
|
4
|
+
import { file } from '../utilities/file.js';
|
|
5
|
+
import { logger } from '../utilities/logger.js';
|
|
6
|
+
|
|
7
|
+
export class RemoveTask extends BaseTask {
|
|
8
|
+
private target: string | string[];
|
|
9
|
+
|
|
10
|
+
constructor(target: string | string[]) {
|
|
11
|
+
super({ name: 'remove', description: 'Remove files and directories' });
|
|
12
|
+
this.target = target;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async run(config: BalmConfig): Promise<void> {
|
|
16
|
+
const targets = Array.isArray(this.target) ? this.target : [this.target];
|
|
17
|
+
const resolvedTargets = targets.map((t) =>
|
|
18
|
+
path.isAbsolute(t) ? t : path.join(config.workspace, t)
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const deleted = await file.remove(resolvedTargets);
|
|
22
|
+
logger.warn('remove', `Removed: ${deleted.join(', ')}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { BaseTask } from '../runner/task.js';
|
|
3
|
+
import { BalmConfig, ReplaceOptions } from '../types/index.js';
|
|
4
|
+
import { Pipeline } from '../pipeline/pipeline.js';
|
|
5
|
+
import { transformReplace } from '../pipeline/transforms/replace.js';
|
|
6
|
+
|
|
7
|
+
export interface ReplaceTaskOptions {
|
|
8
|
+
input: string | string[];
|
|
9
|
+
output: string;
|
|
10
|
+
options: ReplaceOptions | ReplaceOptions[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class ReplaceTask extends BaseTask {
|
|
14
|
+
private taskOptions: ReplaceTaskOptions;
|
|
15
|
+
|
|
16
|
+
constructor(taskOptions: ReplaceTaskOptions) {
|
|
17
|
+
super({ name: 'replace', description: 'Replace content in files' });
|
|
18
|
+
this.taskOptions = taskOptions;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async run(config: BalmConfig): Promise<void> {
|
|
22
|
+
const pipeline = Pipeline.from(this.taskOptions.input, {
|
|
23
|
+
cwd: config.workspace
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
pipeline.pipe(transformReplace(this.taskOptions.options));
|
|
27
|
+
|
|
28
|
+
const outDir = path.isAbsolute(this.taskOptions.output)
|
|
29
|
+
? this.taskOptions.output
|
|
30
|
+
: path.join(config.workspace, this.taskOptions.output);
|
|
31
|
+
|
|
32
|
+
await pipeline.dest(outDir);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { BaseTask } from '../runner/task.js';
|
|
2
|
+
import { BalmConfig, BundlerType } from '../types/index.js';
|
|
3
|
+
import { runWebpack } from '../bundler/webpack.js';
|
|
4
|
+
import { runRollup } from '../bundler/rollup.js';
|
|
5
|
+
import { runEsbuild } from '../bundler/esbuild.js';
|
|
6
|
+
|
|
7
|
+
export class ScriptTask extends BaseTask {
|
|
8
|
+
constructor() {
|
|
9
|
+
super({ name: 'script', description: 'Bundle JavaScript/TypeScript' });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async run(config: BalmConfig): Promise<void> {
|
|
13
|
+
const bundler = config.scripts.bundler;
|
|
14
|
+
|
|
15
|
+
switch (bundler) {
|
|
16
|
+
case BundlerType.rollup:
|
|
17
|
+
await runRollup(config);
|
|
18
|
+
break;
|
|
19
|
+
case BundlerType.esbuild:
|
|
20
|
+
await runEsbuild(config);
|
|
21
|
+
break;
|
|
22
|
+
case BundlerType.webpack:
|
|
23
|
+
default:
|
|
24
|
+
await runWebpack(config);
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import connect from 'connect';
|
|
5
|
+
import serveStatic from 'serve-static';
|
|
6
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
7
|
+
import { createProxyMiddleware } from 'http-proxy-middleware';
|
|
8
|
+
import { BaseTask } from '../runner/task.js';
|
|
9
|
+
import { BalmConfig } from '../types/index.js';
|
|
10
|
+
import { FileWatcher } from '../watcher/index.js';
|
|
11
|
+
import { logger } from '../utilities/logger.js';
|
|
12
|
+
import pc from 'picocolors';
|
|
13
|
+
|
|
14
|
+
export class ServerTask extends BaseTask {
|
|
15
|
+
private watcher: FileWatcher | null = null;
|
|
16
|
+
private wsServer: WebSocketServer | null = null;
|
|
17
|
+
private clients = new Set<WebSocket>();
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
super({ name: 'serve', description: 'Start development server with live reload' });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
broadcast(data: { type: 'reload' | 'reload-css'; file?: string }): void {
|
|
24
|
+
const payload = JSON.stringify(data);
|
|
25
|
+
for (const client of this.clients) {
|
|
26
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
27
|
+
client.send(payload);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async run(config: BalmConfig): Promise<void> {
|
|
33
|
+
if (config.env.isProd) {
|
|
34
|
+
logger.warn('server', 'Development server is disabled in production mode');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const app = connect();
|
|
39
|
+
const port = config.server.port || 3000;
|
|
40
|
+
const host = config.server.host || 'localhost';
|
|
41
|
+
|
|
42
|
+
// Rewrite %PUBLIC_URL% in request URLs
|
|
43
|
+
app.use((req: any, _res: any, next: any) => {
|
|
44
|
+
if (req.url?.includes('%PUBLIC_URL%')) {
|
|
45
|
+
req.url = req.url.replace('/%PUBLIC_URL%', '').replace('%PUBLIC_URL%', '') || '/';
|
|
46
|
+
}
|
|
47
|
+
next();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Dynamic manifest.json replacement for %PUBLIC_URL%
|
|
51
|
+
app.use((req: any, res: any, next: any) => {
|
|
52
|
+
if (req.url === '/manifest.json' || req.url?.endsWith('/manifest.json')) {
|
|
53
|
+
const candidates = [
|
|
54
|
+
path.join(config.dest.base, 'manifest.json'),
|
|
55
|
+
path.join(config.src.base, 'manifest.json'),
|
|
56
|
+
path.join(config.workspace, 'manifest.json')
|
|
57
|
+
];
|
|
58
|
+
const file = candidates.find((f) => fs.existsSync(f));
|
|
59
|
+
if (file) {
|
|
60
|
+
let content = fs.readFileSync(file, 'utf-8');
|
|
61
|
+
const replacement = config.assets.virtualDir ? `/${config.assets.virtualDir}` : '';
|
|
62
|
+
content = content.replaceAll('%PUBLIC_URL%', replacement);
|
|
63
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
64
|
+
return res.end(content);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
next();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Static assets routing
|
|
71
|
+
if (config.paths.source.css) {
|
|
72
|
+
app.use('/' + config.paths.source.css, serveStatic(config.dest.css));
|
|
73
|
+
}
|
|
74
|
+
if (config.paths.tmp.css && config.paths.tmp.css !== config.paths.source.css) {
|
|
75
|
+
app.use('/' + config.paths.tmp.css, serveStatic(config.dest.css));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (config.paths.source.js) {
|
|
79
|
+
app.use('/' + config.paths.source.js, serveStatic(config.dest.js));
|
|
80
|
+
}
|
|
81
|
+
if (config.paths.tmp.js && config.paths.tmp.js !== config.paths.source.js) {
|
|
82
|
+
app.use('/' + config.paths.tmp.js, serveStatic(config.dest.js));
|
|
83
|
+
}
|
|
84
|
+
app.use('/chunk', serveStatic(path.join(config.dest.js, 'chunk')));
|
|
85
|
+
app.use('/chunk', serveStatic(path.join(config.dest.base, 'chunk')));
|
|
86
|
+
|
|
87
|
+
if (config.paths.source.img) {
|
|
88
|
+
app.use(
|
|
89
|
+
'/' + config.paths.source.img,
|
|
90
|
+
serveStatic(path.join(config.src.base, config.paths.source.img))
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (config.paths.tmp.img) {
|
|
94
|
+
app.use('/' + config.paths.tmp.img, serveStatic(config.dest.img));
|
|
95
|
+
}
|
|
96
|
+
app.use('/img', serveStatic(path.join(config.dest.base, 'img')));
|
|
97
|
+
app.use('/img', serveStatic(path.join(config.dest.js, 'img')));
|
|
98
|
+
|
|
99
|
+
if (config.paths.source.font) {
|
|
100
|
+
app.use(
|
|
101
|
+
'/' + config.paths.source.font,
|
|
102
|
+
serveStatic(path.join(config.src.base, config.paths.source.font))
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if (config.paths.tmp.font) {
|
|
106
|
+
app.use('/' + config.paths.tmp.font, serveStatic(config.dest.font));
|
|
107
|
+
}
|
|
108
|
+
app.use('/font', serveStatic(path.join(config.dest.base, 'font')));
|
|
109
|
+
app.use('/font', serveStatic(path.join(config.dest.js, 'font')));
|
|
110
|
+
app.use('/fonts', serveStatic(path.join(config.src.base, 'fonts')));
|
|
111
|
+
app.use('/fonts', serveStatic(path.join(config.dest.base, 'fonts')));
|
|
112
|
+
|
|
113
|
+
app.use(serveStatic(config.dest.base));
|
|
114
|
+
app.use(serveStatic(config.src.base));
|
|
115
|
+
app.use(serveStatic(config.workspace));
|
|
116
|
+
|
|
117
|
+
// History API fallback for SPA
|
|
118
|
+
if (config.server.historyOptions) {
|
|
119
|
+
app.use((req: any, res: any, next: any) => {
|
|
120
|
+
const isGetOrHead = req.method === 'GET' || req.method === 'HEAD';
|
|
121
|
+
const isHtmlNav = !req.url?.includes('.') || req.headers.accept?.includes('text/html');
|
|
122
|
+
if (isGetOrHead && isHtmlNav) {
|
|
123
|
+
const indexPath = path.join(config.dest.html, 'index.html');
|
|
124
|
+
const srcIndexPath = path.join(config.src.base, 'index.html');
|
|
125
|
+
const fileToServe = fs.existsSync(indexPath) ? indexPath : srcIndexPath;
|
|
126
|
+
if (fs.existsSync(fileToServe)) {
|
|
127
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
128
|
+
if (req.method === 'HEAD') {
|
|
129
|
+
return res.end();
|
|
130
|
+
}
|
|
131
|
+
return fs.createReadStream(fileToServe).pipe(res);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
next();
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Proxy middleware
|
|
139
|
+
if (config.server.proxy && typeof config.server.proxy === 'object') {
|
|
140
|
+
const proxyEntries = Array.isArray(config.server.proxy)
|
|
141
|
+
? config.server.proxy
|
|
142
|
+
: [config.server.proxy];
|
|
143
|
+
|
|
144
|
+
for (const entry of proxyEntries) {
|
|
145
|
+
if (entry.context && entry.options) {
|
|
146
|
+
app.use(createProxyMiddleware(entry.options));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const server = http.createServer(app);
|
|
152
|
+
|
|
153
|
+
// WebSocket Live Reload Server
|
|
154
|
+
this.wsServer = new WebSocketServer({ server, path: '/__balm_ws' });
|
|
155
|
+
this.wsServer.on('connection', (socket) => {
|
|
156
|
+
this.clients.add(socket);
|
|
157
|
+
socket.on('close', () => this.clients.delete(socket));
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Start File Watcher
|
|
161
|
+
this.watcher = new FileWatcher(config, (data) => {
|
|
162
|
+
this.broadcast(data);
|
|
163
|
+
});
|
|
164
|
+
this.watcher.start();
|
|
165
|
+
|
|
166
|
+
return new Promise((resolve) => {
|
|
167
|
+
server.listen(port, host, () => {
|
|
168
|
+
console.log('');
|
|
169
|
+
logger.success('server', pc.bold('BalmJS Dev Server is ready!'));
|
|
170
|
+
console.log(` ${pc.green('➜')} ${pc.bold('Local:')} ${pc.cyan(`http://${host}:${port}/`)}`);
|
|
171
|
+
console.log(` ${pc.green('➜')} ${pc.bold('Network:')} ${pc.cyan(`http://127.0.0.1:${port}/`)}`);
|
|
172
|
+
console.log('');
|
|
173
|
+
|
|
174
|
+
if (config.env.isTest) {
|
|
175
|
+
this.watcher?.stop();
|
|
176
|
+
this.wsServer?.close();
|
|
177
|
+
server.close();
|
|
178
|
+
resolve();
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|