Repository navigation
Expand file tree
/
Copy pathgulpfile.js
More file actions
279 lines (252 loc) · 8.36 KB
/
Copy pathgulpfile.js
File metadata and controls
279 lines (252 loc) · 8.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
/* eslint-disable */
/**
* 构建与打包脚本(单次遍历 + 条件处理 + ZIP)。
* 规则:
* - lib/**(第三方库,含 @版本)原样复制;
* - 非 .min.js 的业务 JS:Babel 转 ES5,生产模式下再压缩;
* - 非 .min.css 的 CSS:仅生产模式下压缩;
* - HTML:仅生产模式下压缩,不改写路径;
* - 图片与其它静态资源:直接复制(不再优化);
* - 最后输出到 dist/public 并打包为 dist/public.zip。
*
* 环境变量:
* - NODE_ENV=production:开启 HTML/CSS/JS 压缩;
* - NODE_ENV=development:保留未压缩(仅 JS 做 ES5 转译)。
*/
const gulp = require('gulp');
const gulpIf = require('gulp-if');
const babel = require('gulp-babel');
const terser = require('gulp-terser');
const cleanCSS = require('gulp-clean-css');
const htmlmin = require('gulp-htmlmin');
const zip = require('gulp-zip');
const replace = require('gulp-replace');
const file = require('gulp-file');
const del = require('del');
const path = require('path');
const pkg = require('./package.json');
/** 是否生产模式(NODE_ENV=production) */
const isProd = process.env.NODE_ENV === 'production';
/** 自定义后端接口(用于自动替换) */
const API = process.env.LXS_API;
/** 是否进行转换 */
const isBabel = process.env.LXS_BABEL !== void 0;
/** 源目录(保持与你的项目结构一致) */
const SRC = 'src'; //'../src/server/public';
/** 输出目录 */
const DIST = 'dist/public';
const D2ST = process.env.LXS_DIST;
/** 匹配 conf.js 引入 */
const regConf =
/<script[^>]*\bsrc\s*=\s*["']\s*(?![A-Za-z]+:|\/)(?:\.\/|\.\.\/|[^"'/]+\/)*conf\.js\s*["'][^>]*>\s*<\/script\s*>/;
/** 匹配 js 中判断语句 */
const regJs = /\(typeof api === 'string' && api\) \|\| /;
/**
* 获取文件扩展名(小写)。
* @param {import('vinyl')} file - Vinyl 文件对象。
* @returns {string} 扩展名(如 ".js")
*/
function ext(file) {
return path.extname(file.path).toLowerCase();
}
/**
* 获取基础文件名(小写)。
* @param {import('vinyl')} file - Vinyl 文件对象。
* @returns {string} 基础文件名(如 "outer.min.js")
*/
function base(file) {
return path.basename(file.path).toLowerCase();
}
/**
* 判断文件是否位于 lib 目录(第三方库,含 @版本)。
* 该目录下文件全部原样复制,不做压缩或转译。
* @param {import('vinyl')} file - Vinyl 文件对象。
* @returns {boolean}
*/
function inLib(file) {
const rel = file.relative;
return rel.startsWith('lib') && rel.includes('@');
// const rel = file.relative.replace(/\\/g, '/'); // 兼容 Windows 路径分隔符
// return rel.startsWith('lib/') && rel.includes('@');
}
/**
* 判断是否为已预压缩的 .min.* 文件。
* @param {import('vinyl')} file - Vinyl 文件对象。
* @returns {boolean}
*/
function isMinFile(file) {
return base(file).includes('.min.');
}
/**
* 是否为 HTML 文件。
* @param {import('vinyl')} file - Vinyl 文件对象。
* @returns {boolean}
*/
function isHTML(file) {
return ext(file) === '.html';
}
/**
* 是否为 JS 文件。
* @param {import('vinyl')} file - Vinyl 文件对象。
* @returns {boolean}
*/
function isJS(file) {
return ext(file) === '.js';
}
/**
* 是否为 CSS 文件。
* @param {import('vinyl')} file - Vinyl 文件对象。
* @returns {boolean}
*/
function isCSS(file) {
return ext(file) === '.css';
}
/**
* 是否为图片文件。
* @param {import('vinyl')} file - Vinyl 文件对象。
* @returns {boolean}
*/
function isImage(file) {
return ['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp'].includes(ext(file));
}
/**
* 是否为 WASM 二进制文件。
* @param {import('vinyl')} file - Vinyl 文件对象。
* @returns {boolean}
*/
function isWasmBinary(file) {
return ext(file) === '.wasm';
}
/**
* 是否为 WASM 的 JS 包装文件。
* 为避免功能破坏,这类文件不做转译/压缩。
* @param {import('vinyl')} file - Vinyl 文件对象。
* @returns {boolean}
*/
function isWasmJsWrapper(file) {
const name = base(file);
return name.includes('.wasm.');
// return name.endsWith('.wasm.js') || name.endsWith('.wasm.min.js');
}
/**
* 清理输出目录。
* @returns {Promise<string[]>} 删除的路径列表。
*/
function clean() {
const dirs = ['dist'];
if (D2ST) dirs.push(D2ST);
return del(dirs, { force: Boolean(D2ST) });
}
/**
* 核心任务:单次遍历所有资源,在同一条管线上按条件处理并输出与打包。
* 流程:
* 1. HTML(仅生产压缩);
* 2. JS(非 lib、非 .min、非 wasm 包装 → Babel 转 ES5;生产再压缩);
* 3. CSS(非 lib、非 .min → 仅生产压缩);
* 4. 其余静态资源直接复制;
* 5. 输出到 dist/public,并额外生成 dist/public.zip。
*
* @returns {NodeJS.ReadWriteStream} gulp 处理流。
*/
function action() {
const globs = [`${SRC}/**/*`, `!${SRC}/conf.js`];
// if (!API) globs.push(`!${SRC}/conf.js`);
// console.log(API, API === void 0, API !== void 0, Boolean(API));
const since = gulp.lastRun(action); // 保留用作判断是否首次运行
// console.log('since:', since);
return (
gulp
// 单次读取所有文件(保留原层级)
// 注:设置 encoding: false 保证不会损坏二进制资源
// https://medium.com/gulpjs/announcing-gulp-v5-c67d077dbdb7
.src(globs, {
base: SRC,
allowEmpty: true,
encoding: false,
since: since,
})
// API:自定义接口替换 / 内嵌模式 移除对 conf.js 引用
.pipe(gulpIf((file) => API === void 0 && isHTML(file) && !inLib(file), replace(regConf, '')))
.pipe(
gulpIf(
(file) =>
API === void 0 && !inLib(file) && (isJS(file) ? !isMinFile(file) : isHTML(file)), // 分别检测 js 与 html
replace(regJs, ''),
),
)
.pipe(gulpIf(API !== void 0 && since === void 0, file('conf.js', `var api='${API}'`)))
// HTML:仅生产压缩(保持相对引用)
.pipe(
gulpIf(
(file) => /*isProd &&*/ isHTML(file) && !inLib(file),
htmlmin({
collapseWhitespace: true,
removeComments: true,
removeRedundantAttributes: true,
removeEmptyAttributes: true,
minifyCSS: true,
minifyJS: true,
}),
),
)
// JS:非 lib、非 .min、非 wasm 包装 → 先 Babel 转 ES5,再在生产模式压缩
.pipe(
gulpIf(
(file) => isBabel && isJS(file) && !inLib(file) && !isMinFile(file),
babel({
presets: ['@babel/env'],
}), // 使用 .babelrc,输出 ES5 语法
),
)
.pipe(
gulpIf(
(file) =>
/*isProd &&*/
isJS(file) && !inLib(file) && !isMinFile(file),
terser({
ecma: 5,
mangle: true,
safari10: true,
}),
),
)
// CSS:非 lib、非 .min → 仅生产压缩
.pipe(
gulpIf(
(file) => /*isProd &&*/ isCSS(file) && !inLib(file) && !isMinFile(file),
cleanCSS(), // 注:级别 2 会破坏旧版兼容 css
),
)
// 图片与其他静态资源:直接复制(不再优化)
// 例如:png/jpg/svg/webp、字体、wasm、ico、json、map 等
// 写入到目录
.pipe(gulp.dest(DIST))
.pipe(gulpIf(Boolean(D2ST), gulp.dest(D2ST || (() => ''))))
// 继续在同一流上创建 ZIP(保持目录层级)
.pipe(gulpIf(isProd, zip(`lxsrcs-v${pkg.version}-webui.zip`)))
.pipe(gulpIf(isProd, gulp.dest('dist')))
);
}
/** 测试复制 */
function copy() {
const globs = [`${SRC}/**/*`];
globs.push(`!${SRC}/conf.js`);
return gulp.src(globs, { base: SRC, allowEmpty: true, encoding: false }).pipe(gulp.dest(DIST));
}
/**
* 监听源目录变更,并触发增量构建与打包。
* @returns {void}
*/
function watch() {
gulp.watch(`${SRC}/**/*`, action);
}
/** 构建任务(清理 → 构建+打包) */
const build = gulp.series(clean, action);
/** 开发任务(清理 → 构建+打包 → 监听) */
const dev = gulp.series(clean, action, watch);
// 导出任务(CLI 可用:gulp build / gulp dev / gulp clean)
exports.clean = clean;
exports.build = build;
exports.dev = dev;
exports.test = gulp.series(clean, copy);
exports.default = async () => console.log('Usage: gulp [ build / dev / clean ]');