@qse/edu-scripts 0.0.0-beta.3 → 0.0.0-beta.4
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/babel.config.json +4 -1
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +1440 -0
- package/dist/index.d.mts +89 -0
- package/dist/index.mjs +13 -0
- package/package.json +14 -9
- package/src/{auto-refactor.js → auto-refactor.ts} +6 -10
- package/src/{build.js → build.ts} +15 -7
- package/src/{cli.js → cli.ts} +14 -13
- package/src/{commit-dist.js → commit-dist.ts} +24 -15
- package/src/config/{paths.js → paths.ts} +17 -2
- package/src/config/plugins/mock-server/{index.js → index.ts} +15 -17
- package/src/config/plugins/{postcss-safe-area.js → postcss-safe-area.ts} +4 -2
- package/src/config/webpackConfig.js +12 -12
- package/src/config/webpackDevServerConfig.js +2 -3
- package/src/{deploy.js → deploy.ts} +23 -15
- package/src/{generator.js → generator.ts} +2 -10
- package/src/index.ts +2 -2
- package/src/{start.js → start.ts} +11 -6
- package/src/utils/FileSizeReporter.js +2 -2
- package/src/utils/{appConfig.js → appConfig.ts} +1 -1
- package/src/utils/{beforeStart.js → beforeStart.ts} +14 -5
- package/src/utils/changeDeployVersion.js +3 -3
- package/src/utils/defineConfig.ts +4 -4
- package/src/utils/{exec.js → exec.ts} +1 -1
- package/src/utils/{getConfig.js → getConfig.ts} +6 -4
- package/src/utils/{getOverride.js → getOverride.ts} +6 -8
- package/tsconfig.json +2 -3
- package/tsdown.config.ts +5 -0
- package/docs/.vitepress/config.ts +0 -35
- package/docs/changelog.md +0 -1
- package/docs/debug.md +0 -17
- package/docs/deploy.md +0 -54
- package/docs/faq.md +0 -144
- package/docs/feat.md +0 -167
- package/docs/grayscale.md +0 -31
- package/docs/index.md +0 -15
- package/docs/install.md +0 -1
- package/docs/mode.md +0 -42
- package/docs/override.md +0 -193
- package/docs/refactor-react-16.md +0 -37
- package/docs/refactor.md +0 -67
- package/docs/static.md +0 -24
- package/es/auto-refactor.js +0 -153
- package/es/build.js +0 -58
- package/es/cli.js +0 -65
- package/es/commit-dist.js +0 -83
- package/es/config/paths.js +0 -39
- package/es/config/plugins/mock-server/defineMock.d.ts +0 -6
- package/es/config/plugins/mock-server/defineMock.js +0 -7
- package/es/config/plugins/mock-server/index.js +0 -122
- package/es/config/plugins/postcss-safe-area.js +0 -22
- package/es/config/plugins/ws-utils-createSocketURL.js +0 -98
- package/es/config/webpackConfig.js +0 -444
- package/es/config/webpackDevServerConfig.js +0 -73
- package/es/deploy.js +0 -148
- package/es/generator.js +0 -52
- package/es/index.d.ts +0 -2
- package/es/index.js +0 -7
- package/es/start.js +0 -45
- package/es/utils/FileSizeReporter.js +0 -107
- package/es/utils/appConfig.js +0 -35
- package/es/utils/beforeStart.js +0 -51
- package/es/utils/changeDeployVersion.js +0 -85
- package/es/utils/defineConfig.d.ts +0 -83
- package/es/utils/defineConfig.js +0 -7
- package/es/utils/exec.js +0 -10
- package/es/utils/getConfig.js +0 -23
- package/es/utils/getOverride.js +0 -28
- package/src/asset/dll/libcommon3-manifest.json +0 -181
- package/src/asset/template/edu-app-env.d.ts.tpl +0 -20
- package/src/asset/template/edu-scripts.override.js.tpl +0 -7
- package/src/asset/template/tailwind.config.js.tpl +0 -11
- package/src/asset/template/tsconfig.json.tpl +0 -24
- /package/{es/asset → asset}/dll/libcommon3-manifest.json +0 -0
- /package/{es/asset → asset}/template/edu-app-env.d.ts.tpl +0 -0
- /package/{es/asset → asset}/template/edu-scripts.override.js.tpl +0 -0
- /package/{es/asset → asset}/template/tailwind.config.js.tpl +0 -0
- /package/{es/asset → asset}/template/tsconfig.json.tpl +0 -0
- /package/{src/config/plugins → asset}/ws-utils-createSocketURL.js +0 -0
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,1440 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { globby, globbySync } from "globby";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import semver from "semver";
|
|
9
|
+
import yargs from "yargs";
|
|
10
|
+
import { rspack } from "@rspack/core";
|
|
11
|
+
import { RspackDevServer } from "@rspack/dev-server";
|
|
12
|
+
import { RsdoctorRspackPlugin } from "@rsdoctor/rspack-plugin";
|
|
13
|
+
import ReactRefreshPlugin from "@rspack/plugin-react-refresh";
|
|
14
|
+
import HtmlWebpackPlugin from "html-webpack-plugin";
|
|
15
|
+
import chokidar from "chokidar";
|
|
16
|
+
import { debounce, memoize } from "lodash-es";
|
|
17
|
+
import express from "express";
|
|
18
|
+
import cookieParser from "cookie-parser";
|
|
19
|
+
import multer from "multer";
|
|
20
|
+
import { pathToRegexp } from "path-to-regexp";
|
|
21
|
+
import filesize from "filesize";
|
|
22
|
+
import recursive from "recursive-readdir";
|
|
23
|
+
import stripAnsi from "strip-ansi";
|
|
24
|
+
import { gzipSizeSync } from "gzip-size";
|
|
25
|
+
import { sshSftp } from "@qse/ssh-sftp";
|
|
26
|
+
import { parse, transformFromAstSync, traverse, types } from "@babel/core";
|
|
27
|
+
import ora from "ora";
|
|
28
|
+
import { format } from "prettier";
|
|
29
|
+
import inquirer from "inquirer";
|
|
30
|
+
import cp from "child_process";
|
|
31
|
+
import tmp from "tmp";
|
|
32
|
+
|
|
33
|
+
//#region src/config/paths.ts
|
|
34
|
+
function resolveApp(...filePath) {
|
|
35
|
+
return path.resolve(process.cwd(), ...filePath);
|
|
36
|
+
}
|
|
37
|
+
function resolveOwn(...filePath) {
|
|
38
|
+
let current = path.dirname(fileURLToPath(import.meta.url));
|
|
39
|
+
while (!fs.existsSync(path.join(current, "package.json"))) {
|
|
40
|
+
const parent = path.dirname(current);
|
|
41
|
+
if (parent === current) throw new Error("package.json not found");
|
|
42
|
+
current = parent;
|
|
43
|
+
}
|
|
44
|
+
return path.resolve(current, ...filePath);
|
|
45
|
+
}
|
|
46
|
+
function getExistPath(...paths) {
|
|
47
|
+
for (const path of paths) if (fs.existsSync(path)) return path;
|
|
48
|
+
return paths[0];
|
|
49
|
+
}
|
|
50
|
+
const paths = {
|
|
51
|
+
resolveApp,
|
|
52
|
+
resolveOwn,
|
|
53
|
+
eduAppEnv: resolveApp("src", "edu-app-env.d.ts"),
|
|
54
|
+
dist: resolveApp("dist"),
|
|
55
|
+
sshSftp: resolveApp(".sftprc.json"),
|
|
56
|
+
nodeModules: resolveApp("node_modules"),
|
|
57
|
+
tsconfig: resolveApp("tsconfig.json"),
|
|
58
|
+
jsconfig: resolveApp("jsconfig.json"),
|
|
59
|
+
package: resolveApp("package.json"),
|
|
60
|
+
tailwind: resolveApp("tailwind.config.js"),
|
|
61
|
+
indexJS: resolveApp("src", "index.js"),
|
|
62
|
+
pages: resolveApp("src", "pages"),
|
|
63
|
+
override: resolveApp("edu-scripts.override.js"),
|
|
64
|
+
indexHTML: globbySync("./public/*.html", { absolute: true }),
|
|
65
|
+
src: resolveApp("src"),
|
|
66
|
+
public: resolveApp("public"),
|
|
67
|
+
static: resolveApp("public", "static"),
|
|
68
|
+
theme: getExistPath(resolveApp("theme.json"), resolveApp("theme.js")),
|
|
69
|
+
mock: resolveApp("mock")
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/utils/appConfig.ts
|
|
74
|
+
const appPkg$3 = fs.readJsonSync(paths.package);
|
|
75
|
+
const edu = appPkg$3.edu || {};
|
|
76
|
+
if (edu.single || edu.mainProject) {
|
|
77
|
+
if (edu.single) {
|
|
78
|
+
edu.mode = "single";
|
|
79
|
+
delete edu.single;
|
|
80
|
+
}
|
|
81
|
+
if (edu.mainProject) {
|
|
82
|
+
edu.mode = "main";
|
|
83
|
+
delete edu.mainProject;
|
|
84
|
+
}
|
|
85
|
+
fs.writeFileSync(paths.package, JSON.stringify(appPkg$3, null, 2), "utf-8");
|
|
86
|
+
}
|
|
87
|
+
const appConfig = {
|
|
88
|
+
get mode() {
|
|
89
|
+
return edu.mode;
|
|
90
|
+
},
|
|
91
|
+
get mainProject() {
|
|
92
|
+
return this.mode === "main";
|
|
93
|
+
},
|
|
94
|
+
get single() {
|
|
95
|
+
return this.mode === "single";
|
|
96
|
+
},
|
|
97
|
+
get grayscale() {
|
|
98
|
+
return !!edu.grayscale;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/utils/beforeStart.ts
|
|
104
|
+
const pkg$2 = fs.readJsonSync(paths.resolveOwn("package.json"));
|
|
105
|
+
const appPkg$2 = fs.readJsonSync(paths.package);
|
|
106
|
+
if (semver.valid(appPkg$2.version) === null) {
|
|
107
|
+
console.log(chalk.red(`package.version 不符合 semver 规范 https://docs.npmjs.com/about-semantic-versioning`));
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
switch (appConfig.mode) {
|
|
111
|
+
case "main":
|
|
112
|
+
console.log(chalk.bgMagenta("正在使用教育主工程模式"));
|
|
113
|
+
break;
|
|
114
|
+
case "single":
|
|
115
|
+
console.log(chalk.bgMagenta("正在使用独立项目模式"));
|
|
116
|
+
break;
|
|
117
|
+
default:
|
|
118
|
+
console.log(chalk.bgMagenta("正在使用教育集成模式"));
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
if (appConfig.grayscale) console.log(chalk.bgYellow("正在使用灰度测试模式"));
|
|
122
|
+
if (!(appConfig.single || appConfig.mainProject)) {
|
|
123
|
+
if (fs.existsSync(paths.static)) {
|
|
124
|
+
console.log(chalk.bgYellow("教育集成工程不能含有 public/static, 现已自动删除"));
|
|
125
|
+
try {
|
|
126
|
+
fs.rmSync(paths.static, { recursive: true });
|
|
127
|
+
} catch (e) {}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (!fs.existsSync(paths.public) && !process.argv.includes("auto-refactor")) {
|
|
131
|
+
console.log(chalk.red(`public 文件夹不存在,请先按文档改造项目`));
|
|
132
|
+
console.log(`文档: ${chalk.underline(pkg$2.homepage)}`);
|
|
133
|
+
console.log(`\n使用 ${chalk.green("npx edu-scripts auto-refactor")} 自动改造\n`);
|
|
134
|
+
process.exit(0);
|
|
135
|
+
}
|
|
136
|
+
if (appPkg$2.browserslist) {
|
|
137
|
+
console.log(chalk.yellow("已删除 package.json 中 browserslist,该值由内部自动控制\n"));
|
|
138
|
+
delete appPkg$2.browserslist;
|
|
139
|
+
fs.writeFileSync(paths.package, JSON.stringify(appPkg$2, null, 2), "utf-8");
|
|
140
|
+
}
|
|
141
|
+
if (appPkg$2.eslintConfig) {
|
|
142
|
+
console.log(chalk.yellow("package.json 中 eslintConfig 已废弃,请改用 @qse/eslint-config 进行配置"));
|
|
143
|
+
console.log(chalk.yellow("npm i eslint @qse/eslint-config -D"));
|
|
144
|
+
console.log();
|
|
145
|
+
console.log(chalk.gray("新建 eslint.config.mjs 文件,内容如下:"));
|
|
146
|
+
console.log(chalk.yellow("import config from \"@qse/eslint-config\""));
|
|
147
|
+
console.log(chalk.yellow("export default config"));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/config/plugins/postcss-safe-area.ts
|
|
152
|
+
const expr = new RegExp(`env\\(\\s*(${[
|
|
153
|
+
"safe-area-inset-top",
|
|
154
|
+
"safe-area-inset-bottom",
|
|
155
|
+
"safe-area-inset-left",
|
|
156
|
+
"safe-area-inset-right"
|
|
157
|
+
].join("|")})\\s*,?\\s*([^)]+)?\\s*\\)`, "g");
|
|
158
|
+
const PostcssSafeAreaPlugin = () => {
|
|
159
|
+
return {
|
|
160
|
+
postcssPlugin: "postcss-safe-area",
|
|
161
|
+
Declaration(decl) {
|
|
162
|
+
const fallback = decl.value.replace(expr, (match, param, defaultValue) => defaultValue || "0");
|
|
163
|
+
if (fallback !== decl.value) decl.cloneBefore({ value: fallback });
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region src/config/webpackConfig.js
|
|
170
|
+
const require$2 = createRequire(import.meta.url);
|
|
171
|
+
const appPkg$1 = fs.readJsonSync(paths.package);
|
|
172
|
+
const jsMainPath = appConfig.grayscale ? `${appPkg$1.name}/beta/${appPkg$1.name}` : `${appPkg$1.name}/${appPkg$1.name}`;
|
|
173
|
+
const assetPath = appConfig.grayscale ? `${appPkg$1.name}/beta/${appPkg$1.version}` : `${appPkg$1.name}/${appPkg$1.version}`;
|
|
174
|
+
const cssRegex = /\.css$/;
|
|
175
|
+
const cssModuleRegex = /\.module\.css$/;
|
|
176
|
+
const lessRegex = /\.less$/;
|
|
177
|
+
const lessModuleRegex = /\.module\.less$/;
|
|
178
|
+
const imageInlineSizeLimit = 10 * 1024;
|
|
179
|
+
const qseCDN = (() => {
|
|
180
|
+
const contents = paths.indexHTML.map((url) => fs.readFileSync(url, "utf-8"));
|
|
181
|
+
function include(pattern) {
|
|
182
|
+
const regexp = new RegExp(pattern);
|
|
183
|
+
return contents.some((content) => regexp.test(content));
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
isUseCommon: include(/react16.14.*_common31?.js|react-dev-preset.js/),
|
|
187
|
+
isUseAxios: include(/react16.14.*_axios0.21.1|react-dev-preset.js/),
|
|
188
|
+
isUseMoment: include("moment2.29.1.js"),
|
|
189
|
+
isUseAntd: include("antd3.26.20.js"),
|
|
190
|
+
isUseQsbAntd: include("qsb-antd.min.js"),
|
|
191
|
+
isUseQsbSchemeRender: include("qsb-scheme-render.min.js")
|
|
192
|
+
};
|
|
193
|
+
})();
|
|
194
|
+
/**
|
|
195
|
+
* @param {*} args
|
|
196
|
+
* @param {import('../utils/defineConfig.js').Configuration} override
|
|
197
|
+
*/
|
|
198
|
+
function getWebpackConfig(args, override) {
|
|
199
|
+
const isDev = process.env.NODE_ENV === "development";
|
|
200
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
201
|
+
const getStyleLoaders = (cssOptions, preProcessor) => {
|
|
202
|
+
const loaders = [
|
|
203
|
+
{
|
|
204
|
+
loader: require$2.resolve("style-loader"),
|
|
205
|
+
options: { attributes: {
|
|
206
|
+
"data-module": appPkg$1.name,
|
|
207
|
+
"data-version": appPkg$1.version
|
|
208
|
+
} }
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
loader: require$2.resolve("css-loader"),
|
|
212
|
+
options: cssOptions
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
loader: require$2.resolve("postcss-loader"),
|
|
216
|
+
options: {
|
|
217
|
+
postcssOptions: {
|
|
218
|
+
ident: "postcss",
|
|
219
|
+
config: false,
|
|
220
|
+
plugins: [
|
|
221
|
+
isProd && require$2("cssnano")({ preset: "default" }),
|
|
222
|
+
fs.existsSync(paths.tailwind) && require$2.resolve("tailwindcss"),
|
|
223
|
+
require$2.resolve("postcss-flexbugs-fixes"),
|
|
224
|
+
[require$2.resolve("postcss-preset-env"), { autoprefixer: { flexbox: "no-2009" } }],
|
|
225
|
+
isProd && PostcssSafeAreaPlugin(),
|
|
226
|
+
isProd && [require$2.resolve("postcss-momentum-scrolling"), ["scroll", "auto"]],
|
|
227
|
+
require$2.resolve("postcss-normalize"),
|
|
228
|
+
...override.extraPostCSSPlugins
|
|
229
|
+
].filter(Boolean)
|
|
230
|
+
},
|
|
231
|
+
sourceMap: isDev
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
];
|
|
235
|
+
if (preProcessor === "less-loader") loaders.push({
|
|
236
|
+
loader: require$2.resolve("less-loader"),
|
|
237
|
+
options: {
|
|
238
|
+
lessOptions: {
|
|
239
|
+
javascriptEnabled: true,
|
|
240
|
+
modifyVars: fs.existsSync(paths.theme) ? require$2(paths.theme) : void 0
|
|
241
|
+
},
|
|
242
|
+
sourceMap: true
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
return loaders;
|
|
246
|
+
};
|
|
247
|
+
return {
|
|
248
|
+
context: process.cwd(),
|
|
249
|
+
mode: process.env.NODE_ENV,
|
|
250
|
+
entry: "./src/index",
|
|
251
|
+
target: "browserslist",
|
|
252
|
+
output: {
|
|
253
|
+
filename: appConfig.single ? `js/${jsMainPath}_${appPkg$1.version}.[contenthash:6].js` : `js/${jsMainPath}_${appPkg$1.version}.js`,
|
|
254
|
+
chunkFilename: `js/${assetPath}/[name].[chunkhash:8].js`,
|
|
255
|
+
assetModuleFilename: `images/${assetPath}/[name].[hash:6][ext]`,
|
|
256
|
+
uniqueName: appPkg$1.name,
|
|
257
|
+
publicPath: ""
|
|
258
|
+
},
|
|
259
|
+
externals: Object.assign({}, qseCDN.isUseCommon && {
|
|
260
|
+
react: "React",
|
|
261
|
+
"react-dom": "ReactDOM",
|
|
262
|
+
"natty-fetch": "nattyFetch",
|
|
263
|
+
"natty-storage": "nattyStorage",
|
|
264
|
+
"common-utils": "CommonUtils",
|
|
265
|
+
"@qse/common-utils": "CommonUtils"
|
|
266
|
+
}, qseCDN.isUseAxios && { axios: "axios" }, qseCDN.isUseMoment && { moment: "moment" }, qseCDN.isUseAntd && Object.assign({
|
|
267
|
+
react: "React",
|
|
268
|
+
"react-dom": "ReactDOM",
|
|
269
|
+
moment: "moment",
|
|
270
|
+
antd: "antd"
|
|
271
|
+
}, qseCDN.isUseQsbAntd && {
|
|
272
|
+
"@qse/antd": "qsbAntd",
|
|
273
|
+
"@qsb/antd": "qsbAntd"
|
|
274
|
+
}, qseCDN.isUseQsbSchemeRender && {
|
|
275
|
+
"@qse/scheme-render": "qsbSchemeRender",
|
|
276
|
+
"@qsb/scheme-render": "qsbSchemeRender"
|
|
277
|
+
}), !appConfig.single && Object.assign({
|
|
278
|
+
react: "React",
|
|
279
|
+
"react-dom": "ReactDOM",
|
|
280
|
+
"natty-fetch": "nattyFetch",
|
|
281
|
+
"natty-storage": "nattyStorage",
|
|
282
|
+
"common-utils": "CommonUtils",
|
|
283
|
+
"@qse/common-utils": "CommonUtils",
|
|
284
|
+
moment: "moment",
|
|
285
|
+
antd: "antd"
|
|
286
|
+
}, isProd && {
|
|
287
|
+
"@qse/antd": "qsbAntd",
|
|
288
|
+
"@qsb/antd": "qsbAntd",
|
|
289
|
+
"@qse/scheme-render": "qsbSchemeRender",
|
|
290
|
+
"@qsb/scheme-render": "qsbSchemeRender"
|
|
291
|
+
}), override.externals),
|
|
292
|
+
resolve: {
|
|
293
|
+
alias: {
|
|
294
|
+
"@": paths.src,
|
|
295
|
+
...override.alias
|
|
296
|
+
},
|
|
297
|
+
extensions: [
|
|
298
|
+
".web.js",
|
|
299
|
+
".web.mjs",
|
|
300
|
+
".js",
|
|
301
|
+
".mjs",
|
|
302
|
+
".jsx",
|
|
303
|
+
".ts",
|
|
304
|
+
".tsx",
|
|
305
|
+
".json",
|
|
306
|
+
".wasm"
|
|
307
|
+
]
|
|
308
|
+
},
|
|
309
|
+
stats: false,
|
|
310
|
+
devtool: isDev ? "cheap-module-source-map" : false,
|
|
311
|
+
module: { rules: [{ oneOf: [
|
|
312
|
+
{
|
|
313
|
+
resourceQuery: /raw/,
|
|
314
|
+
type: "asset/source"
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
test: /\.[cm]?[jt]sx?$/,
|
|
318
|
+
exclude: /node_modules/,
|
|
319
|
+
use: [{
|
|
320
|
+
loader: "builtin:swc-loader",
|
|
321
|
+
options: {
|
|
322
|
+
env: { targets: process.env.BROWSERSLIST },
|
|
323
|
+
rspackExperiments: { import: [{
|
|
324
|
+
libraryName: "lodash",
|
|
325
|
+
customName: "lodash/{{member}}"
|
|
326
|
+
}, ...override.import] },
|
|
327
|
+
isModule: "unknown",
|
|
328
|
+
jsc: {
|
|
329
|
+
parser: {
|
|
330
|
+
syntax: "typescript",
|
|
331
|
+
tsx: true,
|
|
332
|
+
decorators: override.decorators
|
|
333
|
+
},
|
|
334
|
+
externalHelpers: true,
|
|
335
|
+
transform: {
|
|
336
|
+
legacyDecorator: override.decorators,
|
|
337
|
+
react: {
|
|
338
|
+
runtime: "automatic",
|
|
339
|
+
development: isDev,
|
|
340
|
+
refresh: isDev
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}]
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
test: /\.[cm]?jsx?$/,
|
|
349
|
+
exclude: /node_modules[\\/](core-js|@swc[\\/]helpers)([\\/]|$)/,
|
|
350
|
+
use: [{
|
|
351
|
+
loader: "builtin:swc-loader",
|
|
352
|
+
options: {
|
|
353
|
+
env: { targets: process.env.BROWSERSLIST },
|
|
354
|
+
isModule: "unknown",
|
|
355
|
+
jsc: {
|
|
356
|
+
parser: { syntax: "ecmascript" },
|
|
357
|
+
externalHelpers: true
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}]
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
test: cssRegex,
|
|
364
|
+
exclude: cssModuleRegex,
|
|
365
|
+
use: getStyleLoaders({
|
|
366
|
+
importLoaders: 1,
|
|
367
|
+
sourceMap: isDev,
|
|
368
|
+
modules: {
|
|
369
|
+
mode: "global",
|
|
370
|
+
localIdentName: "[local]--[hash:base64:6]"
|
|
371
|
+
}
|
|
372
|
+
}),
|
|
373
|
+
sideEffects: true
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
test: cssModuleRegex,
|
|
377
|
+
use: getStyleLoaders({
|
|
378
|
+
importLoaders: 1,
|
|
379
|
+
sourceMap: isDev,
|
|
380
|
+
modules: {
|
|
381
|
+
mode: "local",
|
|
382
|
+
localIdentName: "[local]--[hash:base64:6]"
|
|
383
|
+
}
|
|
384
|
+
})
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
test: lessRegex,
|
|
388
|
+
exclude: lessModuleRegex,
|
|
389
|
+
use: getStyleLoaders({
|
|
390
|
+
importLoaders: 2,
|
|
391
|
+
sourceMap: isDev,
|
|
392
|
+
modules: {
|
|
393
|
+
mode: "global",
|
|
394
|
+
localIdentName: "[local]--[hash:base64:6]"
|
|
395
|
+
}
|
|
396
|
+
}, "less-loader"),
|
|
397
|
+
sideEffects: true
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
test: lessModuleRegex,
|
|
401
|
+
use: getStyleLoaders({
|
|
402
|
+
importLoaders: 2,
|
|
403
|
+
sourceMap: isDev,
|
|
404
|
+
modules: {
|
|
405
|
+
mode: "local",
|
|
406
|
+
localIdentName: "[local]--[hash:base64:6]"
|
|
407
|
+
}
|
|
408
|
+
}, "less-loader")
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
test: /\.(bmp|png|jpe?g|gif|webp)$/,
|
|
412
|
+
type: "asset",
|
|
413
|
+
parser: { dataUrlCondition: { maxSize: imageInlineSizeLimit } }
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
test: /\.svg$/,
|
|
417
|
+
type: "asset",
|
|
418
|
+
parser: { dataUrlCondition: { maxSize: imageInlineSizeLimit } },
|
|
419
|
+
issuer: { and: [/\.(css|less)$/] }
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
test: /\.svg$/,
|
|
423
|
+
use: [{
|
|
424
|
+
loader: require$2.resolve("@svgr/webpack"),
|
|
425
|
+
options: {
|
|
426
|
+
prettier: false,
|
|
427
|
+
svgo: false,
|
|
428
|
+
svgoConfig: { plugins: [{ removeViewBox: false }] },
|
|
429
|
+
titleProp: true,
|
|
430
|
+
ref: false
|
|
431
|
+
}
|
|
432
|
+
}, {
|
|
433
|
+
loader: require$2.resolve("url-loader"),
|
|
434
|
+
options: { limit: imageInlineSizeLimit }
|
|
435
|
+
}],
|
|
436
|
+
issuer: { and: [/\.(ts|tsx|js|jsx|md|mdx)$/] }
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
test: /\.md$/,
|
|
440
|
+
type: "asset/source"
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
test: /\.(?!(?:js|mjs|jsx|ts|tsx|html|json)$)[^.]+$/,
|
|
444
|
+
type: "asset/resource"
|
|
445
|
+
}
|
|
446
|
+
].filter(Boolean) }] },
|
|
447
|
+
plugins: [
|
|
448
|
+
qseCDN.isUseCommon && new rspack.DllReferencePlugin({ manifest: fs.readJsonSync(paths.resolveOwn("asset", "dll", "libcommon3-manifest.json")) }),
|
|
449
|
+
new rspack.NormalModuleReplacementPlugin(/createSocketURL\.js$/, (resource) => {
|
|
450
|
+
if (resource.context.includes("webpack-dev-server")) resource.request = paths.resolveOwn("asset", "ws-utils-createSocketURL.js");
|
|
451
|
+
}),
|
|
452
|
+
new rspack.IgnorePlugin({
|
|
453
|
+
resourceRegExp: /^\.\/locale$/,
|
|
454
|
+
contextRegExp: /moment$/
|
|
455
|
+
}),
|
|
456
|
+
new rspack.DefinePlugin({
|
|
457
|
+
"process.env.APP_NAME": JSON.stringify(appPkg$1.name),
|
|
458
|
+
"process.env.APP_VERSION": JSON.stringify(appPkg$1.version),
|
|
459
|
+
"process.env.BABEL_ENV": JSON.stringify(process.env.BABEL_ENV),
|
|
460
|
+
"process.env.BROWSERSLIST": JSON.stringify(process.env.BROWSERSLIST),
|
|
461
|
+
...override.define
|
|
462
|
+
}),
|
|
463
|
+
new rspack.ProgressPlugin(),
|
|
464
|
+
isDev && new rspack.CaseSensitivePlugin(),
|
|
465
|
+
isDev && new ReactRefreshPlugin({ overlay: false }),
|
|
466
|
+
...isDev || process.env.OUTPUT_HTML || appConfig.single || appConfig.mainProject ? paths.indexHTML.map((template) => new HtmlWebpackPlugin({
|
|
467
|
+
template,
|
|
468
|
+
filename: template.split("/").pop(),
|
|
469
|
+
inject: false,
|
|
470
|
+
minify: {
|
|
471
|
+
removeComments: true,
|
|
472
|
+
collapseWhitespace: true,
|
|
473
|
+
removeRedundantAttributes: true,
|
|
474
|
+
useShortDoctype: true,
|
|
475
|
+
removeEmptyAttributes: true,
|
|
476
|
+
removeStyleLinkTypeAttributes: true,
|
|
477
|
+
keepClosingSlash: true,
|
|
478
|
+
minifyJS: true,
|
|
479
|
+
minifyCSS: true,
|
|
480
|
+
minifyURLs: true
|
|
481
|
+
}
|
|
482
|
+
})) : [],
|
|
483
|
+
process.env.ANALYZE && isProd && new RsdoctorRspackPlugin(),
|
|
484
|
+
isDev && ((compiler) => {
|
|
485
|
+
let isFirst = true;
|
|
486
|
+
compiler.hooks.afterDone.tap("edu-scripts-startup", (stats) => {
|
|
487
|
+
if (!isFirst) console.clear();
|
|
488
|
+
isFirst = false;
|
|
489
|
+
if (override.startup) {
|
|
490
|
+
const logger = compiler.getInfrastructureLogger("edu-scripts");
|
|
491
|
+
override.startup({
|
|
492
|
+
logger,
|
|
493
|
+
chalk,
|
|
494
|
+
compiler
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
console.log(stats.toString({
|
|
498
|
+
preset: "errors-warnings",
|
|
499
|
+
timings: true,
|
|
500
|
+
colors: true
|
|
501
|
+
}));
|
|
502
|
+
});
|
|
503
|
+
})
|
|
504
|
+
].filter(Boolean),
|
|
505
|
+
optimization: {
|
|
506
|
+
minimize: isProd && override.minify !== false,
|
|
507
|
+
minimizer: [new rspack.SwcJsMinimizerRspackPlugin({ minimizerOptions: {
|
|
508
|
+
ecma: 5,
|
|
509
|
+
compress: {
|
|
510
|
+
pure_funcs: override.pure_funcs,
|
|
511
|
+
drop_debugger: true,
|
|
512
|
+
ecma: 5,
|
|
513
|
+
comparisons: false,
|
|
514
|
+
inline: 2
|
|
515
|
+
}
|
|
516
|
+
} })],
|
|
517
|
+
splitChunks: { minChunks: 2 }
|
|
518
|
+
},
|
|
519
|
+
performance: {
|
|
520
|
+
maxEntrypointSize: appConfig.single ? 1024 * 1024 : 30 * 1024,
|
|
521
|
+
maxAssetSize: 2 * 1024 * 1024
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
//#endregion
|
|
527
|
+
//#region src/config/plugins/mock-server/index.ts
|
|
528
|
+
const require$1 = createRequire(import.meta.url);
|
|
529
|
+
const { register } = require$1("@swc-node/register/register");
|
|
530
|
+
let mockCache = {};
|
|
531
|
+
let isSetup = false;
|
|
532
|
+
const setupMock = debounce(function setupMock() {
|
|
533
|
+
mockCache = {};
|
|
534
|
+
const files = globbySync(paths.mock, { expandDirectories: { extensions: ["js", "ts"] } });
|
|
535
|
+
if (isSetup) console.log(chalk.green("Mock files changed, reloaded"));
|
|
536
|
+
else isSetup = true;
|
|
537
|
+
for (const file of files) {
|
|
538
|
+
delete require$1.cache[require$1.resolve(file)];
|
|
539
|
+
try {
|
|
540
|
+
let mock = require$1(file);
|
|
541
|
+
mock = mock.default || mock;
|
|
542
|
+
for (const key in mock) {
|
|
543
|
+
const [method, path] = key.split(" ");
|
|
544
|
+
mockCache[key] = {
|
|
545
|
+
method,
|
|
546
|
+
path,
|
|
547
|
+
handler: mock[key]
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
} catch (e) {
|
|
551
|
+
console.error(chalk.red(`Mock file ${file} error: ${e.message}`));
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}, 100);
|
|
555
|
+
const getPathReAndKeys = memoize((path) => {
|
|
556
|
+
const keys = [];
|
|
557
|
+
return {
|
|
558
|
+
re: pathToRegexp(path, keys),
|
|
559
|
+
keys
|
|
560
|
+
};
|
|
561
|
+
});
|
|
562
|
+
function decodeParam(val) {
|
|
563
|
+
if (typeof val !== "string" || val.length === 0) return val;
|
|
564
|
+
try {
|
|
565
|
+
return decodeURIComponent(val);
|
|
566
|
+
} catch (err) {
|
|
567
|
+
if (err instanceof URIError) {
|
|
568
|
+
err.message = `Failed to decode param ' ${val} '`;
|
|
569
|
+
err.status = 400;
|
|
570
|
+
err.statusCode = 400;
|
|
571
|
+
}
|
|
572
|
+
throw err;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* @type {import('express').RequestHandler}
|
|
577
|
+
*/
|
|
578
|
+
const mockMiddlewave = function mockMiddlewave(req, res, next) {
|
|
579
|
+
const { method, path } = req;
|
|
580
|
+
for (const key in mockCache) {
|
|
581
|
+
const mock = mockCache[key];
|
|
582
|
+
if (mock.method !== method) continue;
|
|
583
|
+
const { keys, re } = getPathReAndKeys(mock.path);
|
|
584
|
+
const m = re.exec(path);
|
|
585
|
+
if (m) {
|
|
586
|
+
console.log(chalk.green(`Mock: ${key}`));
|
|
587
|
+
res.setHeader("X-Mock", key);
|
|
588
|
+
if (typeof mock.handler === "function") {
|
|
589
|
+
const params = {};
|
|
590
|
+
for (let i = 1; i < m.length; i += 1) {
|
|
591
|
+
const prop = keys[i - 1].name;
|
|
592
|
+
const val = decodeParam(m[i]);
|
|
593
|
+
if (val !== void 0) params[prop] = val;
|
|
594
|
+
}
|
|
595
|
+
req.params = params;
|
|
596
|
+
const middelwaves = [
|
|
597
|
+
express.urlencoded({
|
|
598
|
+
limit: "5mb",
|
|
599
|
+
extended: true
|
|
600
|
+
}),
|
|
601
|
+
express.json({
|
|
602
|
+
limit: "5mb",
|
|
603
|
+
strict: false
|
|
604
|
+
}),
|
|
605
|
+
multer().any(),
|
|
606
|
+
cookieParser(),
|
|
607
|
+
mock.handler
|
|
608
|
+
];
|
|
609
|
+
const throwNext = () => {
|
|
610
|
+
console.log(chalk.red(`Mock: ${key} don't use next()`));
|
|
611
|
+
res.sendStatus(500);
|
|
612
|
+
};
|
|
613
|
+
const mwNext = () => {
|
|
614
|
+
middelwaves.shift()(req, res, middelwaves.length ? mwNext : throwNext);
|
|
615
|
+
};
|
|
616
|
+
mwNext();
|
|
617
|
+
} else res.json(mock.handler);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
next();
|
|
622
|
+
};
|
|
623
|
+
function setupMockServer(middelwaves, _devServer) {
|
|
624
|
+
if (!fs.existsSync(paths.mock)) return;
|
|
625
|
+
register();
|
|
626
|
+
middelwaves.unshift({
|
|
627
|
+
name: "edu-scripts-mock-middelwave",
|
|
628
|
+
middleware: mockMiddlewave
|
|
629
|
+
});
|
|
630
|
+
console.log(`<i> ${chalk.green.bold("[edu-scripts] Mock server created:")} ${chalk.cyan.bold(paths.mock)}`);
|
|
631
|
+
chokidar.watch(paths.mock).on("all", setupMock);
|
|
632
|
+
return middelwaves;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
//#endregion
|
|
636
|
+
//#region src/config/webpackDevServerConfig.js
|
|
637
|
+
function createProxy(context, target, origin) {
|
|
638
|
+
const url = new URL(origin || target);
|
|
639
|
+
return {
|
|
640
|
+
context: [context],
|
|
641
|
+
target,
|
|
642
|
+
changeOrigin: true,
|
|
643
|
+
onProxyReq: (proxyReq) => {
|
|
644
|
+
proxyReq.setHeader("host", url.host);
|
|
645
|
+
proxyReq.setHeader("origin", url.origin);
|
|
646
|
+
proxyReq.removeHeader("referer");
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* @param {*} args
|
|
652
|
+
* @param {import('../utils/defineConfig.js').Configuration} override
|
|
653
|
+
*/
|
|
654
|
+
function getWebpackDevServerConfig(args, override) {
|
|
655
|
+
const host = process.env.HOST || "0.0.0.0";
|
|
656
|
+
/** @type {import('@rspack/dev-server').Configuration} */
|
|
657
|
+
const devServer = {
|
|
658
|
+
allowedHosts: "all",
|
|
659
|
+
historyApiFallback: true,
|
|
660
|
+
port: args.port,
|
|
661
|
+
host,
|
|
662
|
+
client: {
|
|
663
|
+
webSocketURL: "auto://0.0.0.0:0/ws",
|
|
664
|
+
overlay: {
|
|
665
|
+
runtimeErrors: false,
|
|
666
|
+
errors: true,
|
|
667
|
+
warnings: false
|
|
668
|
+
}
|
|
669
|
+
},
|
|
670
|
+
headers: {
|
|
671
|
+
"Access-Control-Allow-Origin": "*",
|
|
672
|
+
"Access-Control-Allow-Methods": "*",
|
|
673
|
+
"Access-Control-Allow-Headers": "*"
|
|
674
|
+
},
|
|
675
|
+
setupMiddlewares: (middlewares, devServer) => {
|
|
676
|
+
if (override.mock !== false) setupMockServer(middlewares, devServer);
|
|
677
|
+
return middlewares;
|
|
678
|
+
},
|
|
679
|
+
proxy: [createProxy("/api", "http://192.168.10.19:3339/qsxxwapdev", "http://www.zhidianbao.cn")],
|
|
680
|
+
compress: true
|
|
681
|
+
};
|
|
682
|
+
if (override.proxy) {
|
|
683
|
+
if (Array.isArray(override.proxy)) devServer.proxy = [...override.proxy, ...devServer.proxy];
|
|
684
|
+
else if (typeof override.proxy === "object") devServer.proxy = [...Object.entries(override.proxy).map(([context, target]) => {
|
|
685
|
+
return createProxy(context, target);
|
|
686
|
+
}), ...devServer.proxy];
|
|
687
|
+
else throw new Error("proxy 必须是数组或对象");
|
|
688
|
+
const proxyMap = /* @__PURE__ */ new Map();
|
|
689
|
+
devServer.proxy = devServer.proxy.filter((item) => {
|
|
690
|
+
const key = JSON.stringify([...item.context].sort());
|
|
691
|
+
if (!proxyMap.has(key)) {
|
|
692
|
+
proxyMap.set(key, true);
|
|
693
|
+
return true;
|
|
694
|
+
}
|
|
695
|
+
return false;
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
return devServer;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
//#endregion
|
|
702
|
+
//#region src/utils/getOverride.ts
|
|
703
|
+
const require = createRequire(import.meta.url);
|
|
704
|
+
const defaultOverride = {
|
|
705
|
+
minify: true,
|
|
706
|
+
proxy: [],
|
|
707
|
+
extraPostCSSPlugins: [],
|
|
708
|
+
import: [],
|
|
709
|
+
pure_funcs: ["console.log"]
|
|
710
|
+
};
|
|
711
|
+
let override = null;
|
|
712
|
+
function getOverride() {
|
|
713
|
+
if (override) return override;
|
|
714
|
+
override = Object.assign({}, defaultOverride);
|
|
715
|
+
if (fs.existsSync(paths.override)) {
|
|
716
|
+
const userOverride = require(paths.override);
|
|
717
|
+
if (typeof userOverride !== "object") throw new Error("格式错误,请使用 npx edu g override 生成文件");
|
|
718
|
+
Object.assign(override, userOverride);
|
|
719
|
+
}
|
|
720
|
+
return override;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
//#endregion
|
|
724
|
+
//#region src/utils/getConfig.ts
|
|
725
|
+
function getConfig(args) {
|
|
726
|
+
const override = getOverride();
|
|
727
|
+
let webpackConfig = getWebpackConfig(args, override);
|
|
728
|
+
if (override.webpack) webpackConfig = override.webpack(webpackConfig) || webpackConfig;
|
|
729
|
+
if (process.env.NODE_ENV === "development") {
|
|
730
|
+
let devServerConfig = getWebpackDevServerConfig(args, override);
|
|
731
|
+
if (override.devServer) devServerConfig = override.devServer(devServerConfig) || devServerConfig;
|
|
732
|
+
webpackConfig.devServer = devServerConfig;
|
|
733
|
+
}
|
|
734
|
+
return webpackConfig;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
//#endregion
|
|
738
|
+
//#region src/start.ts
|
|
739
|
+
async function start(args) {
|
|
740
|
+
process.env.NODE_ENV = "development";
|
|
741
|
+
process.env.BABEL_ENV = "development";
|
|
742
|
+
process.env.BROWSERSLIST = "chrome >= 70";
|
|
743
|
+
process.env.WEBPACK_DEV_SERVER_BASE_PORT = "3000";
|
|
744
|
+
const basePort = process.env.WEBPACK_DEV_SERVER_BASE_PORT;
|
|
745
|
+
const port = await RspackDevServer.getFreePort(args.port || process.env.PORT);
|
|
746
|
+
if (!(args.port || process.env.PORT) && +port !== +basePort) console.log(chalk.bgYellow(`${basePort} 端口已被占用,现切换到 ${port} 端口运行`));
|
|
747
|
+
args.port = port;
|
|
748
|
+
process.env.PORT = port;
|
|
749
|
+
const config = getConfig(args);
|
|
750
|
+
const compiler = rspack(config);
|
|
751
|
+
const middleware = rspack.lazyCompilationMiddleware(compiler);
|
|
752
|
+
const oldSetupMiddlewares = config.devServer.setupMiddlewares;
|
|
753
|
+
config.devServer.setupMiddlewares = (middlewares, devServer) => {
|
|
754
|
+
if (oldSetupMiddlewares) middlewares = oldSetupMiddlewares(middlewares, devServer);
|
|
755
|
+
middlewares.unshift(middleware);
|
|
756
|
+
return middlewares;
|
|
757
|
+
};
|
|
758
|
+
const devServer = new RspackDevServer(config.devServer, compiler);
|
|
759
|
+
devServer.start();
|
|
760
|
+
["SIGINT", "SIGTERM"].forEach(function(sig) {
|
|
761
|
+
process.on(sig, function() {
|
|
762
|
+
devServer.stop();
|
|
763
|
+
process.exit();
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
if (process.env.CI !== "true") process.stdin.on("end", function() {
|
|
767
|
+
devServer.stop();
|
|
768
|
+
process.exit();
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
//#endregion
|
|
773
|
+
//#region src/utils/FileSizeReporter.js
|
|
774
|
+
/**
|
|
775
|
+
* Copyright (c) 2015-present, Facebook, Inc.
|
|
776
|
+
*
|
|
777
|
+
* This source code is licensed under the MIT license found in the
|
|
778
|
+
* LICENSE file in the root directory of this source tree.
|
|
779
|
+
*/
|
|
780
|
+
function canReadAsset(asset) {
|
|
781
|
+
return /\.(js|css)$/.test(asset) && !/service-worker\.js/.test(asset) && !/precache-manifest\.[0-9a-f]+\.js/.test(asset);
|
|
782
|
+
}
|
|
783
|
+
function printFileSizesAfterBuild(webpackStats, previousSizeMap, buildFolder, maxBundleGzipSize, maxChunkGzipSize) {
|
|
784
|
+
var root = previousSizeMap.root;
|
|
785
|
+
var sizes = previousSizeMap.sizes;
|
|
786
|
+
var assets = (webpackStats.stats || [webpackStats]).map((stats) => stats.toJson({
|
|
787
|
+
all: false,
|
|
788
|
+
assets: true
|
|
789
|
+
}).assets.filter((asset) => canReadAsset(asset.name)).map((asset) => {
|
|
790
|
+
var size = gzipSizeSync(fs.readFileSync(path.join(root, asset.name)));
|
|
791
|
+
var previousSize = sizes[removeFileNameHash(root, asset.name)];
|
|
792
|
+
var difference = getDifferenceLabel(size, previousSize);
|
|
793
|
+
return {
|
|
794
|
+
folder: path.join(path.basename(buildFolder), path.dirname(asset.name)),
|
|
795
|
+
name: path.basename(asset.name),
|
|
796
|
+
size,
|
|
797
|
+
sizeLabel: filesize(size) + (difference ? " (" + difference + ")" : "")
|
|
798
|
+
};
|
|
799
|
+
})).reduce((single, all) => all.concat(single), []);
|
|
800
|
+
if (assets.length === 0) return;
|
|
801
|
+
console.log("\ngzip 后文件大小:\n");
|
|
802
|
+
assets.sort((a, b) => b.size - a.size);
|
|
803
|
+
var mainAssetIdx = assets.findIndex((asset) => /_\d+\.\d+\.\d+/.test(asset.name));
|
|
804
|
+
assets.unshift(assets.splice(mainAssetIdx, 1)[0]);
|
|
805
|
+
var longestSizeLabelLength = Math.max.apply(null, assets.map((a) => stripAnsi(a.sizeLabel).length));
|
|
806
|
+
var suggestBundleSplitting = false;
|
|
807
|
+
assets.forEach((asset) => {
|
|
808
|
+
var sizeLabel = asset.sizeLabel;
|
|
809
|
+
var sizeLength = stripAnsi(sizeLabel).length;
|
|
810
|
+
if (sizeLength < longestSizeLabelLength) {
|
|
811
|
+
var rightPadding = " ".repeat(longestSizeLabelLength - sizeLength);
|
|
812
|
+
sizeLabel += rightPadding;
|
|
813
|
+
}
|
|
814
|
+
var isMainBundle = /_\d+\.\d+\.\d+\./.test(asset.name);
|
|
815
|
+
var maxRecommendedSize = isMainBundle ? maxBundleGzipSize : maxChunkGzipSize;
|
|
816
|
+
var isLarge = maxRecommendedSize && asset.size > maxRecommendedSize;
|
|
817
|
+
if (isLarge && path.extname(asset.name) === ".js") suggestBundleSplitting = true;
|
|
818
|
+
console.log(" " + (isLarge ? chalk.yellow(sizeLabel) : sizeLabel) + " " + chalk.dim(asset.folder + path.sep) + chalk.cyan(asset.name));
|
|
819
|
+
if (isMainBundle) console.log("");
|
|
820
|
+
});
|
|
821
|
+
if (suggestBundleSplitting) {
|
|
822
|
+
console.log();
|
|
823
|
+
console.log(chalk.yellow("产物大小明显大于推荐的大小 (主文件 30k, chunk 1M, 黄色标注为偏大)"));
|
|
824
|
+
console.log(chalk.yellow("考虑下使用代码分割解决"));
|
|
825
|
+
console.log(chalk.yellow("也可以使用 npm run analyze 命令分析产物"));
|
|
826
|
+
}
|
|
827
|
+
console.log();
|
|
828
|
+
}
|
|
829
|
+
function removeFileNameHash(buildFolder, fileName) {
|
|
830
|
+
return fileName.replace(buildFolder, "").replace(/\\/g, "/").replace(/\/\d+\.\d+\.\d+\//, "/").replace(/\/?(.*)(\.[0-9a-f]+)(\.chunk)?(\.js|\.css)/, (match, p1, p2, p3, p4) => p1 + p4);
|
|
831
|
+
}
|
|
832
|
+
function getDifferenceLabel(currentSize, previousSize) {
|
|
833
|
+
var FIFTY_KILOBYTES = 1024 * 50;
|
|
834
|
+
var difference = currentSize - previousSize;
|
|
835
|
+
var fileSize = !Number.isNaN(difference) ? filesize(difference) : 0;
|
|
836
|
+
if (difference >= FIFTY_KILOBYTES) return chalk.red("+" + fileSize);
|
|
837
|
+
else if (difference < FIFTY_KILOBYTES && difference > 0) return chalk.yellow("+" + fileSize);
|
|
838
|
+
else if (difference < 0) return chalk.green(fileSize);
|
|
839
|
+
else return "";
|
|
840
|
+
}
|
|
841
|
+
function measureFileSizesBeforeBuild(buildFolder) {
|
|
842
|
+
return new Promise((resolve) => {
|
|
843
|
+
recursive(buildFolder, (err, fileNames) => {
|
|
844
|
+
var sizes;
|
|
845
|
+
if (!err && fileNames) sizes = fileNames.filter(canReadAsset).reduce((memo, fileName) => {
|
|
846
|
+
var contents = fs.readFileSync(fileName);
|
|
847
|
+
var key = removeFileNameHash(buildFolder, fileName);
|
|
848
|
+
memo[key] = gzipSizeSync(contents);
|
|
849
|
+
return memo;
|
|
850
|
+
}, {});
|
|
851
|
+
resolve({
|
|
852
|
+
root: buildFolder,
|
|
853
|
+
sizes: sizes || {}
|
|
854
|
+
});
|
|
855
|
+
});
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
//#endregion
|
|
860
|
+
//#region src/build.ts
|
|
861
|
+
const WARN_AFTER_BUNDLE_GZIP_SIZE = appConfig.single ? 1024 * 1024 : 30 * 1024;
|
|
862
|
+
const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
|
|
863
|
+
async function build(args) {
|
|
864
|
+
process.env.NODE_ENV = "production";
|
|
865
|
+
process.env.BABEL_ENV = "production";
|
|
866
|
+
process.env.BROWSERSLIST = ">0.2%, iOS>=9, ie 11, chrome>=49, not op_mini all";
|
|
867
|
+
if (args.analyze) process.env.ANALYZE = "1";
|
|
868
|
+
if (args.outputHtml) process.env.OUTPUT_HTML = "1";
|
|
869
|
+
const previousSizeMap = await measureFileSizesBeforeBuild(paths.dist);
|
|
870
|
+
fs.emptyDirSync(paths.dist);
|
|
871
|
+
if (appConfig.single) fs.copySync(paths.public, paths.resolveApp("dist"));
|
|
872
|
+
if (appConfig.mainProject && fs.existsSync(paths.static)) fs.copySync(paths.static, paths.resolveApp("dist", "static"));
|
|
873
|
+
rspack(getConfig(args), (error, stats) => {
|
|
874
|
+
if (error) {
|
|
875
|
+
console.log(chalk.red("编译失败"));
|
|
876
|
+
console.log(chalk.red(error.message || error));
|
|
877
|
+
process.exit(1);
|
|
878
|
+
}
|
|
879
|
+
if (!stats) return;
|
|
880
|
+
if (stats.compilation.errors.length) {
|
|
881
|
+
console.log(chalk.red("编译失败"));
|
|
882
|
+
console.log(stats.toString({
|
|
883
|
+
all: false,
|
|
884
|
+
errors: true,
|
|
885
|
+
colors: true
|
|
886
|
+
}));
|
|
887
|
+
process.exit(1);
|
|
888
|
+
}
|
|
889
|
+
console.log(stats.toString({
|
|
890
|
+
colors: true,
|
|
891
|
+
preset: "errors-warnings",
|
|
892
|
+
timings: true
|
|
893
|
+
}));
|
|
894
|
+
printFileSizesAfterBuild(stats, previousSizeMap, paths.dist, WARN_AFTER_BUNDLE_GZIP_SIZE, WARN_AFTER_CHUNK_GZIP_SIZE);
|
|
895
|
+
if (appConfig.single) console.log(`打包完成,可以使用 ${chalk.green("@qse/ssh-sftp")} 自动部署代码到 v1`);
|
|
896
|
+
else console.log(`打包完成,可以运行 ${chalk.green("npx edu-scripts deploy")} 部署代码到 v1`);
|
|
897
|
+
console.log();
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
//#endregion
|
|
902
|
+
//#region src/utils/changeDeployVersion.js
|
|
903
|
+
const TARGET_IDENTIFIER_NAME = "project_apiArr";
|
|
904
|
+
const MODULE_IDENTIFIER_NAME = "module";
|
|
905
|
+
function changeDeployVersion(code, pkg) {
|
|
906
|
+
const { name, version, grayscale } = pkg;
|
|
907
|
+
let ast;
|
|
908
|
+
try {
|
|
909
|
+
ast = parse(code, { filename: "ver.js" });
|
|
910
|
+
} catch (error) {
|
|
911
|
+
throw new Error(`代码解析错误: ${error.message}`, { cause: error });
|
|
912
|
+
}
|
|
913
|
+
const keyName = grayscale ? "grayscale" : "main";
|
|
914
|
+
/**
|
|
915
|
+
* @return {babel.NodePath<t.VariableDeclarator> | undefined}
|
|
916
|
+
*/
|
|
917
|
+
function findTargetDeclarator(ast) {
|
|
918
|
+
let res;
|
|
919
|
+
traverse(ast, { VariableDeclarator(path) {
|
|
920
|
+
if (types.isIdentifier(path.node.id, { name: TARGET_IDENTIFIER_NAME }) && types.isArrayExpression(path.node.init)) res = path;
|
|
921
|
+
} });
|
|
922
|
+
return res;
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* @param {babel.NodePath<t.VariableDeclarator>} path
|
|
926
|
+
* @return {babel.NodePath<t.ObjectExpression> | undefined}
|
|
927
|
+
*/
|
|
928
|
+
function findModuleObject(path) {
|
|
929
|
+
let res;
|
|
930
|
+
path.traverse({ ObjectExpression(path) {
|
|
931
|
+
if (path.node.properties.some((node) => types.isIdentifier(node.key, { name: MODULE_IDENTIFIER_NAME }) && types.isLiteral(node.value, { value: name }))) res = path;
|
|
932
|
+
} });
|
|
933
|
+
return res;
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* @param {babel.NodePath<t.ObjectExpression>} path
|
|
937
|
+
*/
|
|
938
|
+
function modifyModuleVersion(path) {
|
|
939
|
+
let hasModify = false;
|
|
940
|
+
path.traverse({ Property(path) {
|
|
941
|
+
if (path.node.key.name === keyName) {
|
|
942
|
+
path.node.value.value = version;
|
|
943
|
+
hasModify = true;
|
|
944
|
+
}
|
|
945
|
+
} });
|
|
946
|
+
if (!hasModify) path.node.properties.push(types.objectProperty(types.identifier(keyName), types.stringLiteral(version)));
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* @param {babel.NodePath<t.ObjectExpression>} path
|
|
950
|
+
*/
|
|
951
|
+
function deleteModuleComments(path) {
|
|
952
|
+
path.traverse({ ObjectExpression(path) {
|
|
953
|
+
delete path.node.leadingComments;
|
|
954
|
+
} });
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* @param {babel.NodePath<t.VariableDeclarator>} path
|
|
958
|
+
*/
|
|
959
|
+
function addModuleToTarget(path) {
|
|
960
|
+
const elements = path.node.init.elements;
|
|
961
|
+
elements.splice(elements.length - 2, 0, types.objectExpression([types.objectProperty(types.identifier(MODULE_IDENTIFIER_NAME), types.stringLiteral(name)), types.objectProperty(types.identifier(keyName), types.stringLiteral(version))]));
|
|
962
|
+
}
|
|
963
|
+
const targetPath = findTargetDeclarator(ast);
|
|
964
|
+
if (!targetPath) throw new Error(`ver.js 不合规范,未找到参数 ${TARGET_IDENTIFIER_NAME}`);
|
|
965
|
+
const moduleObjPath = findModuleObject(targetPath);
|
|
966
|
+
if (moduleObjPath) modifyModuleVersion(moduleObjPath);
|
|
967
|
+
else addModuleToTarget(targetPath);
|
|
968
|
+
deleteModuleComments(targetPath);
|
|
969
|
+
return transformFromAstSync(ast, void 0, {
|
|
970
|
+
filename: "ver.js",
|
|
971
|
+
minified: true
|
|
972
|
+
}).code;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
//#endregion
|
|
976
|
+
//#region src/deploy.ts
|
|
977
|
+
const appPkg = fs.readJsonSync(paths.package);
|
|
978
|
+
const baseConfig = {
|
|
979
|
+
localPath: "dist",
|
|
980
|
+
ignore: [],
|
|
981
|
+
cleanRemoteFiles: false,
|
|
982
|
+
securityLock: false,
|
|
983
|
+
keepAlive: true,
|
|
984
|
+
noWarn: true
|
|
985
|
+
};
|
|
986
|
+
async function normalDeploy(args) {
|
|
987
|
+
const resolve = paths.resolveApp;
|
|
988
|
+
/**
|
|
989
|
+
* 生成路径
|
|
990
|
+
* @name remoteFile 远程文件
|
|
991
|
+
* @name tmpFile 本地文件
|
|
992
|
+
* @name tmpDir 本地临时文件夹 `tmpFile`存在的文件夹
|
|
993
|
+
* @name tmpBase 本地临时文件夹 到`__tmp__`层
|
|
994
|
+
* @param {string} remoteFilePath 远程文件相对`opts.remotePath`地址
|
|
995
|
+
*/
|
|
996
|
+
function getLocalAndRemoteFilePath(remoteFilePath, opts) {
|
|
997
|
+
const splited = remoteFilePath.split("/");
|
|
998
|
+
const fileName = splited[splited.length - 1];
|
|
999
|
+
const tmpBase = resolve(opts.localPath, "__tmp__");
|
|
1000
|
+
const tmpDir = resolve(opts.localPath, "__tmp__", ...splited.slice(0, -1));
|
|
1001
|
+
return {
|
|
1002
|
+
tmpDir,
|
|
1003
|
+
tmpFile: resolve(tmpDir, fileName),
|
|
1004
|
+
remoteFile: [opts.remotePath, remoteFilePath].join("/"),
|
|
1005
|
+
tmpBase
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
function dateTime() {
|
|
1009
|
+
let date = /* @__PURE__ */ new Date();
|
|
1010
|
+
date = /* @__PURE__ */ new Date(date.getTime() - date.getTimezoneOffset() * 6e4);
|
|
1011
|
+
return date.toISOString().replace(/T/, " ").replace(/\..+/, "");
|
|
1012
|
+
}
|
|
1013
|
+
function updateLogContent(content, info) {
|
|
1014
|
+
const lines = content.trim().split("\n");
|
|
1015
|
+
lines.push(`[${dateTime()}] ${JSON.stringify(info)}\n`);
|
|
1016
|
+
return lines.slice(-50).join("\n");
|
|
1017
|
+
}
|
|
1018
|
+
async function upload(opts) {
|
|
1019
|
+
const { sftp, opts: fullOpts } = await sshSftp(opts);
|
|
1020
|
+
const spinner = ora("自动更新 ver.js 版本配置").start();
|
|
1021
|
+
const { remoteFile, tmpDir, tmpFile, tmpBase } = getLocalAndRemoteFilePath("js/ver.js", fullOpts);
|
|
1022
|
+
try {
|
|
1023
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
1024
|
+
const info = {
|
|
1025
|
+
name: appPkg.name,
|
|
1026
|
+
version: appPkg.version,
|
|
1027
|
+
grayscale: appConfig.grayscale
|
|
1028
|
+
};
|
|
1029
|
+
{
|
|
1030
|
+
await sftp.fastGet(remoteFile, tmpFile);
|
|
1031
|
+
let code = await fs.readFile(tmpFile, { encoding: "utf-8" });
|
|
1032
|
+
code = await changeDeployVersion(code, info);
|
|
1033
|
+
code = await format(code, {
|
|
1034
|
+
parser: "babel",
|
|
1035
|
+
printWidth: 120
|
|
1036
|
+
});
|
|
1037
|
+
await sftp.fastPut(tmpFile, remoteFile + ".bak");
|
|
1038
|
+
await fs.writeFile(tmpFile, code);
|
|
1039
|
+
await sftp.fastPut(tmpFile, remoteFile);
|
|
1040
|
+
}
|
|
1041
|
+
{
|
|
1042
|
+
const remoteLogFile = remoteFile + ".log";
|
|
1043
|
+
const tmpLogFile = tmpFile + ".log";
|
|
1044
|
+
let content = "";
|
|
1045
|
+
try {
|
|
1046
|
+
await sftp.fastGet(remoteLogFile, tmpLogFile);
|
|
1047
|
+
content = await fs.readFile(tmpLogFile, "utf-8");
|
|
1048
|
+
} catch (_error) {}
|
|
1049
|
+
content = updateLogContent(content, info);
|
|
1050
|
+
await fs.writeFile(tmpLogFile, content);
|
|
1051
|
+
await sftp.fastPut(tmpLogFile, remoteLogFile);
|
|
1052
|
+
}
|
|
1053
|
+
spinner.succeed("已更新 ver.js 版本配置");
|
|
1054
|
+
} catch (e) {
|
|
1055
|
+
spinner.fail(`自动修改 ver.js 失败,请手动修改`);
|
|
1056
|
+
console.log(chalk.bgRed(e.message));
|
|
1057
|
+
} finally {
|
|
1058
|
+
await sftp.end();
|
|
1059
|
+
fs.removeSync(tmpBase);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
const presetConfig = {
|
|
1063
|
+
s: { preset: {
|
|
1064
|
+
context: "eduwebngv1",
|
|
1065
|
+
folder: "userportal"
|
|
1066
|
+
} },
|
|
1067
|
+
b: { preset: {
|
|
1068
|
+
context: "eduwebngv1",
|
|
1069
|
+
folder: "bureaupc"
|
|
1070
|
+
} },
|
|
1071
|
+
d: { preset: {
|
|
1072
|
+
context: "eduwebngv1",
|
|
1073
|
+
folder: "documentshelves"
|
|
1074
|
+
} },
|
|
1075
|
+
c: { preset: {
|
|
1076
|
+
context: "eduwebngv1",
|
|
1077
|
+
folder: "compositionshelves"
|
|
1078
|
+
} },
|
|
1079
|
+
cd: {
|
|
1080
|
+
preset: { server: "19" },
|
|
1081
|
+
remotePath: "/erp/edumaven/dingcorrection-page-dev/compositionshelves"
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1084
|
+
const uploadSftpConfigs = [];
|
|
1085
|
+
if (args.bureau) uploadSftpConfigs.push(presetConfig.b);
|
|
1086
|
+
if (args.school) uploadSftpConfigs.push(presetConfig.s);
|
|
1087
|
+
if (args.documentshelves) uploadSftpConfigs.push(presetConfig.d);
|
|
1088
|
+
if (args.compositionshelves) uploadSftpConfigs.push(presetConfig.c);
|
|
1089
|
+
if (args.compositionshelvesDingtalk) uploadSftpConfigs.push(presetConfig.cd);
|
|
1090
|
+
if (uploadSftpConfigs.length === 0) {
|
|
1091
|
+
console.log(`
|
|
1092
|
+
${chalk.red("指定 deploy 部署范围")}
|
|
1093
|
+
执行 ${chalk.green("npx edu-scripts deploy -h")} 查看具体用法
|
|
1094
|
+
`);
|
|
1095
|
+
process.exit();
|
|
1096
|
+
}
|
|
1097
|
+
const uploadConfig = {
|
|
1098
|
+
...baseConfig,
|
|
1099
|
+
ignore: [...baseConfig.ignore, "js/ver.js"]
|
|
1100
|
+
};
|
|
1101
|
+
if (!appConfig.mainProject) uploadConfig.ignore = [...uploadConfig.ignore, "!(js|images)"];
|
|
1102
|
+
for (const config of uploadSftpConfigs) await upload({
|
|
1103
|
+
...uploadConfig,
|
|
1104
|
+
...config
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
async function singleDeploy() {
|
|
1108
|
+
sshSftp(fs.existsSync(paths.sshSftp) ? fs.readJsonSync(paths.sshSftp) : {
|
|
1109
|
+
...baseConfig,
|
|
1110
|
+
preset: { context: "qsxxwapdev" },
|
|
1111
|
+
cleanRemoteFiles: true,
|
|
1112
|
+
securityLock: true,
|
|
1113
|
+
keepAlive: false,
|
|
1114
|
+
noWarn: false
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
function deploy(args) {
|
|
1118
|
+
if (appConfig.single) singleDeploy();
|
|
1119
|
+
else normalDeploy(args);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
//#endregion
|
|
1123
|
+
//#region src/auto-refactor.ts
|
|
1124
|
+
const pkg$1 = fs.readJsonSync(paths.resolveOwn("package.json"));
|
|
1125
|
+
async function step(msg, callback) {
|
|
1126
|
+
const spinner = ora(msg).start();
|
|
1127
|
+
try {
|
|
1128
|
+
await callback(spinner);
|
|
1129
|
+
spinner.succeed();
|
|
1130
|
+
} catch (error) {
|
|
1131
|
+
spinner.fail();
|
|
1132
|
+
throw error;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
async function autoRefactor() {
|
|
1136
|
+
if (await fs.pathExists(paths.public)) {
|
|
1137
|
+
console.log(chalk.green("已完成改造,不需要重复执行,如果运行报错请查看文档"));
|
|
1138
|
+
console.log(`文档: ${chalk.underline(pkg$1.homepage)}`);
|
|
1139
|
+
process.exit(0);
|
|
1140
|
+
}
|
|
1141
|
+
const appPkg = fs.readJsonSync(paths.package);
|
|
1142
|
+
const answers = await inquirer.prompt([
|
|
1143
|
+
{
|
|
1144
|
+
type: "input",
|
|
1145
|
+
name: "name",
|
|
1146
|
+
message: "请输入模块名称,要求唯一,不能与其他工程相同",
|
|
1147
|
+
default: appPkg.name,
|
|
1148
|
+
validate: (v) => /^[a-z][a-z0-9_-]+$/.test(v) || "请按格式输入 /^[a-z][a-z0-9_-]+$/ 只能使用小写字母、连字符、下划线"
|
|
1149
|
+
},
|
|
1150
|
+
{
|
|
1151
|
+
type: "input",
|
|
1152
|
+
name: "version",
|
|
1153
|
+
message: "版本号",
|
|
1154
|
+
default: "1.0.0",
|
|
1155
|
+
validate: (v) => /^\d+\.\d+\.\d+$/.test(v) || "请按格式输入 /^\\d+\\.\\d+\\.\\d+$/ 例如: 1.0.0"
|
|
1156
|
+
},
|
|
1157
|
+
{
|
|
1158
|
+
type: "list",
|
|
1159
|
+
name: "mode",
|
|
1160
|
+
message: "项目使用的哪种模式",
|
|
1161
|
+
choices: [
|
|
1162
|
+
{
|
|
1163
|
+
name: "教育子工程模式",
|
|
1164
|
+
value: ""
|
|
1165
|
+
},
|
|
1166
|
+
{
|
|
1167
|
+
name: "教育主工程模式",
|
|
1168
|
+
value: "main"
|
|
1169
|
+
},
|
|
1170
|
+
{
|
|
1171
|
+
name: "独立模式",
|
|
1172
|
+
value: "single"
|
|
1173
|
+
}
|
|
1174
|
+
]
|
|
1175
|
+
},
|
|
1176
|
+
{
|
|
1177
|
+
type: "checkbox",
|
|
1178
|
+
name: "deploy",
|
|
1179
|
+
message: "项目需要部署到哪里",
|
|
1180
|
+
when: (ans) => ans.mode !== "single",
|
|
1181
|
+
choices: [
|
|
1182
|
+
{
|
|
1183
|
+
name: "校端",
|
|
1184
|
+
value: "-s"
|
|
1185
|
+
},
|
|
1186
|
+
{
|
|
1187
|
+
name: "局端",
|
|
1188
|
+
value: "-b"
|
|
1189
|
+
},
|
|
1190
|
+
{
|
|
1191
|
+
name: "公文端",
|
|
1192
|
+
value: "-d"
|
|
1193
|
+
}
|
|
1194
|
+
],
|
|
1195
|
+
validate: (v) => !!v && v.length > 0 || "必须选一个"
|
|
1196
|
+
}
|
|
1197
|
+
]);
|
|
1198
|
+
appPkg.name = answers.name;
|
|
1199
|
+
appPkg.version = answers.version;
|
|
1200
|
+
await step("创建 public 文件夹", async () => {
|
|
1201
|
+
await fs.mkdir(paths.public);
|
|
1202
|
+
});
|
|
1203
|
+
await step("移动 js 文件夹", async () => {
|
|
1204
|
+
if (await fs.pathExists(path.resolve(paths.src, "js"))) await fs.move(path.resolve(paths.src, "js"), path.resolve(paths.public, "js"));
|
|
1205
|
+
});
|
|
1206
|
+
await step("移动 html 文件", async () => {
|
|
1207
|
+
const HTMLFiles = await globby("*.html", { cwd: paths.src });
|
|
1208
|
+
for (const file of HTMLFiles) await fs.move(path.resolve(paths.src, file), path.resolve(paths.public, file));
|
|
1209
|
+
});
|
|
1210
|
+
await step("删除 dll 文件夹", async () => {
|
|
1211
|
+
if (await fs.pathExists(path.resolve(paths.src, "dll"))) await fs.remove(path.resolve(paths.src, "dll"));
|
|
1212
|
+
});
|
|
1213
|
+
await step("删除没用的 babel 和 webpack 配置", async () => {
|
|
1214
|
+
const deleteFiles = [
|
|
1215
|
+
...await globby("{.,*}babel*"),
|
|
1216
|
+
...await globby("*webpack*"),
|
|
1217
|
+
...await globby("package-lock.json"),
|
|
1218
|
+
paths.nodeModules,
|
|
1219
|
+
paths.sshSftp
|
|
1220
|
+
];
|
|
1221
|
+
for (const filePath of deleteFiles) await fs.remove(filePath);
|
|
1222
|
+
});
|
|
1223
|
+
await step("设置项目模式", () => {
|
|
1224
|
+
if (answers.mode) appPkg.edu = { mode: answers.mode };
|
|
1225
|
+
});
|
|
1226
|
+
await step("修改 package.json 的 scripts", () => {
|
|
1227
|
+
const scripts = appPkg.scripts;
|
|
1228
|
+
scripts.start = "edu-scripts start";
|
|
1229
|
+
scripts.build = "edu-scripts build";
|
|
1230
|
+
scripts.analyze = "edu-scripts build --analyze";
|
|
1231
|
+
if (answers.mode !== "single") scripts.deploy = `edu-scripts deploy ${answers.deploy.join(" ")}`;
|
|
1232
|
+
else {
|
|
1233
|
+
scripts.deploy = `edu-scripts deploy`;
|
|
1234
|
+
scripts["commit-dist"] = "edu-scripts commit-dist --rm-local";
|
|
1235
|
+
}
|
|
1236
|
+
scripts["one-key-deploy"] = "npm version patch";
|
|
1237
|
+
scripts.postversion = "npm run build && npm run deploy";
|
|
1238
|
+
});
|
|
1239
|
+
await step("删除 babel/webpack 相关依赖", (spinner) => {
|
|
1240
|
+
const deleteRe = /(babel|autoprefixer|webpack|loader|less|css|sass|hmr|ssh-sftp|regenerator-runtime|nowa|prettier)/i;
|
|
1241
|
+
const deletePkgs = {
|
|
1242
|
+
dependencies: [],
|
|
1243
|
+
devDependencies: []
|
|
1244
|
+
};
|
|
1245
|
+
Object.entries(deletePkgs).forEach(([scope, pkgs]) => {
|
|
1246
|
+
for (const key in appPkg[scope]) if (Object.hasOwnProperty.call(appPkg[scope], key)) {
|
|
1247
|
+
if (deleteRe.test(key)) {
|
|
1248
|
+
pkgs.push(key);
|
|
1249
|
+
delete appPkg[scope][key];
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
spinner.clear();
|
|
1254
|
+
console.log(`删除的pkgs\n${chalk.green(JSON.stringify(deletePkgs, null, 2))}\n`);
|
|
1255
|
+
appPkg.devDependencies["@qse/edu-scripts"] = "^" + pkg$1.version;
|
|
1256
|
+
});
|
|
1257
|
+
await fs.writeFile(paths.package, JSON.stringify(appPkg, null, 2), "utf-8");
|
|
1258
|
+
console.log(chalk.green(`
|
|
1259
|
+
改造还未完成,剩余步骤请查看文档
|
|
1260
|
+
${pkg$1.homepage}#/refactor
|
|
1261
|
+
|
|
1262
|
+
运行 npm i 安装依赖
|
|
1263
|
+
运行 edu-scripts start 启动服务
|
|
1264
|
+
`));
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
//#endregion
|
|
1268
|
+
//#region src/commit-dist.ts
|
|
1269
|
+
const exec = (cmd, opts) => cp.execSync(cmd, {
|
|
1270
|
+
encoding: "utf-8",
|
|
1271
|
+
stdio: "pipe",
|
|
1272
|
+
...opts
|
|
1273
|
+
});
|
|
1274
|
+
function validateSVNRoot(root) {
|
|
1275
|
+
const ls = exec(`svn ls ${root}`);
|
|
1276
|
+
return ["trunk", "branches"].every((s) => ls.includes(s));
|
|
1277
|
+
}
|
|
1278
|
+
function getWorkingCopyInfo() {
|
|
1279
|
+
exec(`svn up`);
|
|
1280
|
+
const url = exec(`svn info --show-item url`).trim();
|
|
1281
|
+
const revision = exec(`svn info --show-item last-changed-revision`).trim();
|
|
1282
|
+
const author = exec(`svn info --show-item last-changed-author`).trim();
|
|
1283
|
+
let branch = "trunk";
|
|
1284
|
+
let root = url.replace(/\/trunk$/, "");
|
|
1285
|
+
if (url.includes("/branches/")) {
|
|
1286
|
+
branch = url.split("/").pop();
|
|
1287
|
+
root = url.replace(/\/branches\/[^/]+$/, "");
|
|
1288
|
+
}
|
|
1289
|
+
let distBranchURL = root + "/branches/dist";
|
|
1290
|
+
let distBranchDirURL = distBranchURL + "/" + branch;
|
|
1291
|
+
if (!validateSVNRoot(root)) {
|
|
1292
|
+
console.log(chalk.red("SVN目录不符合规则,必须包含 trunk branches"));
|
|
1293
|
+
process.exit(1);
|
|
1294
|
+
}
|
|
1295
|
+
return {
|
|
1296
|
+
url,
|
|
1297
|
+
branch,
|
|
1298
|
+
revision,
|
|
1299
|
+
author,
|
|
1300
|
+
distBranchURL,
|
|
1301
|
+
distBranchDirURL,
|
|
1302
|
+
root
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
function copyDistToRepo(info) {
|
|
1306
|
+
const tmpdir = tmp.dirSync().name;
|
|
1307
|
+
try {
|
|
1308
|
+
exec(`svn ls ${info.distBranchDirURL} --depth empty`);
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
if (error.message.includes("non-existent")) exec(`svn mkdir ${info.distBranchDirURL} --parents -m "[edu-scripts] create ${info.branch} dist"`);
|
|
1311
|
+
else throw error;
|
|
1312
|
+
}
|
|
1313
|
+
exec(`svn co ${info.distBranchDirURL} ${tmpdir}`);
|
|
1314
|
+
try {
|
|
1315
|
+
exec(`svn rm * --force -q`, { cwd: tmpdir });
|
|
1316
|
+
} catch (_e) {}
|
|
1317
|
+
fs.copySync(paths.dist, tmpdir);
|
|
1318
|
+
exec(`svn add * --force --auto-props --parents --depth infinity -q`, { cwd: tmpdir });
|
|
1319
|
+
exec(`svn ci -m "${`[edu-scripts] commit ${info.branch} dist #${info.revision} @${info.author}`}"`, { cwd: tmpdir });
|
|
1320
|
+
fs.removeSync(tmpdir);
|
|
1321
|
+
}
|
|
1322
|
+
async function commitDist(args) {
|
|
1323
|
+
if (!fs.existsSync(paths.dist)) {
|
|
1324
|
+
console.log(chalk.red("未找到 dist 文件夹,请先 edu-scpirts build"));
|
|
1325
|
+
process.exit(1);
|
|
1326
|
+
}
|
|
1327
|
+
if (exec("svn st").trim().length) {
|
|
1328
|
+
console.log(chalk.red("似乎存在未提交的代码,请提交后重试。运行 svn st 查看具体信息"));
|
|
1329
|
+
process.exit(1);
|
|
1330
|
+
}
|
|
1331
|
+
const info = getWorkingCopyInfo();
|
|
1332
|
+
console.log(chalk.green([
|
|
1333
|
+
`分支: ${info.branch}`,
|
|
1334
|
+
`版本: ${info.revision}`,
|
|
1335
|
+
`作者: ${info.author}`,
|
|
1336
|
+
`地址: ${info.distBranchDirURL}`
|
|
1337
|
+
].join("\n")));
|
|
1338
|
+
copyDistToRepo(info);
|
|
1339
|
+
if (args.rmLocal) fs.removeSync(paths.dist);
|
|
1340
|
+
console.log(chalk.green("提交完成"));
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
//#endregion
|
|
1344
|
+
//#region src/generator.ts
|
|
1345
|
+
const getTmpPath = (...args) => paths.resolveOwn("asset", "template", ...args);
|
|
1346
|
+
async function generatorOverride() {
|
|
1347
|
+
if (fs.existsSync(paths.override)) {
|
|
1348
|
+
console.log(chalk.red(`文件已存在 ${paths.override}`));
|
|
1349
|
+
process.exit(0);
|
|
1350
|
+
}
|
|
1351
|
+
fs.copySync(getTmpPath("edu-scripts.override.js.tpl"), paths.override);
|
|
1352
|
+
console.log(chalk.green(`成功生成 ${paths.override}`));
|
|
1353
|
+
}
|
|
1354
|
+
async function generatorTsconfig() {
|
|
1355
|
+
if (fs.existsSync(paths.tsconfig)) {
|
|
1356
|
+
console.log(chalk.red(`文件已存在 ${paths.tsconfig}`));
|
|
1357
|
+
process.exit(0);
|
|
1358
|
+
}
|
|
1359
|
+
fs.copySync(getTmpPath("tsconfig.json.tpl"), paths.tsconfig);
|
|
1360
|
+
fs.copySync(getTmpPath("edu-app-env.d.ts.tpl"), paths.eduAppEnv);
|
|
1361
|
+
console.log(chalk.green(`成功生成 ${paths.tsconfig}`));
|
|
1362
|
+
}
|
|
1363
|
+
async function generatorTailwind() {
|
|
1364
|
+
if (fs.existsSync(paths.tailwind)) {
|
|
1365
|
+
console.log(chalk.red(`文件已存在 ${paths.tailwind}`));
|
|
1366
|
+
process.exit(0);
|
|
1367
|
+
}
|
|
1368
|
+
fs.copySync(getTmpPath("tailwind.config.js.tpl"), paths.tailwind);
|
|
1369
|
+
const code = [
|
|
1370
|
+
"@tailwind base;",
|
|
1371
|
+
"@tailwind components;",
|
|
1372
|
+
"@tailwind utilities;"
|
|
1373
|
+
].join("\n");
|
|
1374
|
+
const globalLessFile = paths.resolveApp("src", "index.less");
|
|
1375
|
+
if (fs.existsSync(globalLessFile)) {
|
|
1376
|
+
const content = fs.readFileSync(globalLessFile, "utf-8");
|
|
1377
|
+
if (!content.includes("@tailwind base")) fs.writeFileSync(globalLessFile, [code, content].join("\n"));
|
|
1378
|
+
console.log(chalk.green(`成功生成 ${paths.tailwind}`));
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
console.log(chalk.green([
|
|
1382
|
+
`成功生成 ${paths.tailwind}`,
|
|
1383
|
+
"",
|
|
1384
|
+
"添加以下代码到入口处的 index.less 中",
|
|
1385
|
+
code
|
|
1386
|
+
].join("\n")));
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
//#endregion
|
|
1390
|
+
//#region src/cli.ts
|
|
1391
|
+
const pkg = fs.readJsonSync(paths.resolveOwn("package.json"));
|
|
1392
|
+
yargs(process.argv.slice(2)).usage(`教育工程化 webpack5 基础框架\n文档: ${pkg.homepage}`).command("start", "开发", (yargs) => yargs.option("port", {
|
|
1393
|
+
alias: "p",
|
|
1394
|
+
desc: "指定端口",
|
|
1395
|
+
string: true
|
|
1396
|
+
}), (args) => start(args)).command("build", "打包", (yargs) => yargs.option("analyze", {
|
|
1397
|
+
alias: "a",
|
|
1398
|
+
desc: "分析代码",
|
|
1399
|
+
default: false,
|
|
1400
|
+
boolean: true
|
|
1401
|
+
}).option("output-html", {
|
|
1402
|
+
alias: "o",
|
|
1403
|
+
desc: "输出 html 文件",
|
|
1404
|
+
default: false,
|
|
1405
|
+
boolean: true
|
|
1406
|
+
}), (args) => build(args)).command("deploy", "自动部署 dist 到 v1 服务器", (yargs) => yargs.option("school", {
|
|
1407
|
+
alias: "s",
|
|
1408
|
+
desc: "上传到校端",
|
|
1409
|
+
default: false,
|
|
1410
|
+
boolean: true
|
|
1411
|
+
}).option("bureau", {
|
|
1412
|
+
alias: "b",
|
|
1413
|
+
desc: "上传到局端",
|
|
1414
|
+
default: false,
|
|
1415
|
+
boolean: true
|
|
1416
|
+
}).option("documentshelves", {
|
|
1417
|
+
alias: "d",
|
|
1418
|
+
desc: "上传到公文",
|
|
1419
|
+
default: false,
|
|
1420
|
+
boolean: true
|
|
1421
|
+
}).option("compositionshelves", {
|
|
1422
|
+
alias: "c",
|
|
1423
|
+
desc: "上传到文曲智阅",
|
|
1424
|
+
default: false,
|
|
1425
|
+
boolean: true
|
|
1426
|
+
}).option("compositionshelves-dingtalk", {
|
|
1427
|
+
alias: "cd",
|
|
1428
|
+
desc: "上传到文曲智阅(钉钉一方化)",
|
|
1429
|
+
default: false,
|
|
1430
|
+
boolean: true
|
|
1431
|
+
}), (args) => deploy(args)).command(["generator", "g"], "自动生成代码", (yargs) => yargs.command("override", "创建 override 文件", {}, () => generatorOverride()).command("tailwind", "创建 tailwind 文件", {}, () => generatorTailwind()).command("ts", "创建 tsconfig 文件", {}, () => generatorTsconfig()).showHelpOnFail(true).demandCommand(1, "")).command("auto-refactor", "自动改造项目", {}, () => autoRefactor()).command("commit-dist", "提交 dist 目录到 dist 分支", (yargs) => yargs.option("rm-local", {
|
|
1432
|
+
desc: "提交完后删除本地 dist",
|
|
1433
|
+
type: "boolean"
|
|
1434
|
+
}), (args) => commitDist(args)).showHelpOnFail(true).demandCommand(1, "").alias({
|
|
1435
|
+
v: "version",
|
|
1436
|
+
h: "help"
|
|
1437
|
+
}).argv;
|
|
1438
|
+
|
|
1439
|
+
//#endregion
|
|
1440
|
+
export { };
|