hw-weapp-compiler 1.0.21 → 1.0.23

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/README.md CHANGED
@@ -1,119 +1,76 @@
1
- # hw-weapp-compiler
1
+ # weapp-compiler
2
2
 
3
- 基于 webpack 5 的原生微信小程序构建工具,支持 JavaScript、Less、WXML、WXS、分包、npm 组件和可选的 OBS/OSS 静态资源上传。
4
3
 
5
- ## 环境要求
4
+ ## 版本改动
6
5
 
7
- - Node.js 18~24
8
- - 项目源码位于 `src/`
9
- - 必须存在 `src/app.json` 和 `src/app.js`
6
+ 1. **OpenSSL 3.0 兼容**: Node.js >= 17 时自动注入 `--openssl-legacy-provider`
7
+ 2. **wxml-loader 空值守卫**: `loadModule` 失败时不再崩溃
10
8
 
11
9
  ## 安装
12
10
 
13
11
  ```bash
14
- npm install hw-weapp-compiler@1 -D
15
- ```
16
-
17
- 升级版本后如果出现 `Cannot find module`,请删除业务项目中旧的安装产物并重新执行 `npm install`,确保传递依赖与 lockfile 同步。
18
-
19
- 在业务项目的 `package.json` 中添加:
20
-
21
- ```json
22
- {
23
- "scripts": {
24
- "dev": "hw-weapp dev",
25
- "build": "hw-weapp build"
26
- }
27
- }
12
+ npm i weapp-compiler@2.x -D
28
13
  ```
29
14
 
30
15
  ## 使用
31
16
 
32
17
  ```bash
33
- # 开发监听,默认 development
34
- npm run dev
35
-
36
- # 指定预发或生产业务环境
37
- npm run dev -- --simulation
38
- npm run dev -- --production
39
-
40
- # 生产构建,默认 production
41
- npm run build
42
- npm run build -- --development
43
- npm run build -- --simulation
18
+ # 开发调试
19
+ npm run dev # 测试环境
20
+ npm run dev -s # 预发环境
21
+ npm run dev -p # 生产环境
22
+
23
+ # 生产发布
24
+ npm run build -d # 测试环境
25
+ npm run build -s # 预发环境
26
+ npm run build # 生产环境
44
27
  ```
45
28
 
46
- 可用选项:
47
-
48
- - `-d, --development`:测试环境
49
- - `-s, --simulation`:预发环境
50
- - `-p, --production`:生产环境
51
- - `-a, --analyzer`:打开 webpack bundle analyzer
52
- - `-q, --quiet`:关闭 webpack 进度输出
53
-
54
- 最终环境会通过 `process.env.BUILD_ENV` 注入代码,并记录到 `dist/weapp.env.json`。
55
-
56
29
  ## 配置
57
30
 
58
- 在项目根目录创建 `.weapp.js`,最小配置为:
59
-
60
- ```js
61
- module.exports = {};
62
- ```
63
-
64
- 完整示例:
31
+ 项目根目录下创建 `.weapp.js`:
65
32
 
66
33
  ```js
67
34
  const path = require('path');
68
35
 
69
36
  module.exports = {
37
+ // 路径别名
70
38
  alias: {
71
39
  '@utils': path.resolve(__dirname, 'src/utils'),
40
+ '@config': path.resolve(__dirname, 'src/config'),
72
41
  },
42
+ // 资源公共路径
73
43
  publicPath: 'https://cdn.example.com/weapp/',
74
- copyFiles: [
75
- { from: 'images', to: 'images' },
76
- ],
77
- };
78
- ```
79
-
80
- ## 静态资源上传
81
-
82
- 上传是可选功能,只能配置 OBS 或 OSS 中的一种。生产构建会等待上传完成,任何上传失败都会让构建失败;编译失败时不会上传半成品。
83
-
84
- 建议从环境变量读取凭据,避免把密钥提交到仓库:
85
-
86
- ```js
87
- module.exports = {
88
- obsConfig: process.env.OBS_ACCESS_KEY_ID
89
- ? {
90
- access_key_id: process.env.OBS_ACCESS_KEY_ID,
91
- secret_access_key: process.env.OBS_SECRET_ACCESS_KEY,
92
- server: process.env.OBS_SERVER,
93
- bucket: process.env.OBS_BUCKET,
94
- dir: 'weapp',
95
- }
96
- : undefined,
44
+ // 要同步的目录
45
+ copyFiles: [{
46
+ from: 'images',
47
+ to: 'images',
48
+ }],
49
+ // OBS 或 OSS 配置(二选一)
50
+ obsConfig: {
51
+ access_key_id: 'XXX',
52
+ secret_access_key: 'XXX',
53
+ server: 'XXX',
54
+ bucket: 'XXX',
55
+ dir: 'weapp',
56
+ },
57
+ ossConfig: {
58
+ region: '<Your region>',
59
+ accessKeyId: '<Your AccessKeyId>',
60
+ accessKeySecret: '<Your AccessKeySecret>',
61
+ bucket: '<Your Bucket>',
62
+ dir: 'weapp',
63
+ },
97
64
  };
98
65
  ```
99
66
 
100
- OSS 配置字段为 `region`、`accessKeyId`、`accessKeySecret`、`bucket` 和 `dir`。
101
-
102
- ## 开发监听限制
103
-
104
- 修改已有文件可以正常增量构建。开发监听期间新增页面、组件、分包,或后创建同名 `.less`、`.wxss`、`.wxml`、`.json` 文件后,需要重新启动 `dev`。生产 `build` 每次都会重新读取完整项目配置。
105
-
106
- ## 微信开发者工具配置
107
-
108
- 如果资源使用 CDN 上传,可以在 `src/project.config.json` 中忽略本地产物:
67
+ `project.config.json` 中忽略构建产物:
109
68
 
110
69
  ```json
111
- {
112
- "packOptions": {
113
- "ignore": [
114
- { "type": "folder", "value": "assets" },
115
- { "type": "regexp", "value": "\\.map$" }
116
- ]
117
- }
70
+ "packOptions": {
71
+ "ignore": [
72
+ { "type": "folder", "value": "assets" },
73
+ { "type": "regexp", "value": "\\.map$" }
74
+ ]
118
75
  }
119
76
  ```
@@ -56,9 +56,6 @@ ossConfig: {
56
56
  `),
57
57
  );
58
58
  }
59
- if (config.obsConfig && config.ossConfig) {
60
- throw new TypeError('.weapp.js 不能同时配置 obsConfig 和 ossConfig');
61
- }
62
59
  const alias = {
63
60
  ...(config.alias || {}),
64
61
  };
package/build/dev.js CHANGED
@@ -1,5 +1,4 @@
1
1
  const webpack = require('webpack');
2
- const chalk = require('chalk');
3
2
  const { ENV } = require('./config/constants');
4
3
  const webpackConfig = require('./webpack.config');
5
4
  const { createCompilerHandler } = require('./utils/createCompilerHandler');
@@ -21,7 +20,7 @@ module.exports = async (opts) => {
21
20
  ),
22
21
  );
23
22
 
24
- const watching = compiler.watch(
23
+ compiler.watch(
25
24
  {
26
25
  aggregateTimeout: 300,
27
26
  poll: false,
@@ -29,9 +28,4 @@ module.exports = async (opts) => {
29
28
  },
30
29
  createCompilerHandler('dev'),
31
30
  );
32
-
33
- if (!opts || opts.quiet !== true) {
34
- console.log(chalk.cyan('dev 正在监听文件变化'));
35
- }
36
- return watching;
37
31
  };
@@ -9,25 +9,10 @@ const { RawSource } = require('webpack-sources');
9
9
 
10
10
  const { addToUploadQueue } = require('../utils/upload');
11
11
  const { compatiblePath } = require('../utils/compatiblePath');
12
- const { ENV, PATTERNS, ASSET_EXT_PATTERN, ASSETS_DIR } = require('../config/constants');
12
+ const { PATTERNS, ASSET_EXT_PATTERN, ASSETS_DIR } = require('../config/constants');
13
13
 
14
14
  const pluginName = 'WeappCompilerPlugin';
15
15
 
16
- async function uploadCompilationAssets(stats, compiler, assets, upload = addToUploadQueue) {
17
- if (stats.hasErrors() || assets.length === 0) {
18
- return;
19
- }
20
-
21
- try {
22
- await upload(assets);
23
- } catch (error) {
24
- if (compiler.options.mode === ENV.PROD) {
25
- throw error;
26
- }
27
- compiler.getInfrastructureLogger(pluginName).error(error);
28
- }
29
- }
30
-
31
16
  /**
32
17
  * 检测产物中存在哪些公共模块和分包模块
33
18
  */
@@ -91,19 +76,13 @@ function injectGlobalModules(content, assetName, flags) {
91
76
 
92
77
  // runtime 暴露到全局
93
78
  if (assetName === 'runtime.js') {
94
- const runtimeCachePattern = /\b(var|let|const)\s+__webpack_module_cache__\s*=\s*\{\};/;
95
- if (!runtimeCachePattern.test(result)) {
96
- throw new Error(
97
- '无法注入微信小程序 runtime:当前 webpack 产物中缺少 module cache 标记,请检查 webpack 版本',
98
- );
99
- }
100
79
  result = result.replace(
101
- runtimeCachePattern,
102
- (statement, declaration) => `
80
+ 'var __webpack_module_cache__ = {};',
81
+ `
103
82
  if (!global.__webpack_module_cache__) {
104
83
  global.__webpack_module_cache__ = {};
105
84
  }
106
- ${declaration} __webpack_module_cache__ = global.__webpack_module_cache__;
85
+ var __webpack_module_cache__ = global.__webpack_module_cache__;
107
86
  global.__webpack_require__ = __webpack_require__;
108
87
  `,
109
88
  );
@@ -188,10 +167,9 @@ class WeappPlugin {
188
167
  apply(compiler) {
189
168
  let obsAssets = [];
190
169
 
191
- compiler.hooks.done.tapPromise(pluginName, async (stats) => {
192
- const assets = Array.from(new Set(obsAssets));
170
+ compiler.hooks.done.tap(pluginName, () => {
171
+ addToUploadQueue([...obsAssets]);
193
172
  obsAssets = [];
194
- await uploadCompilationAssets(stats, compiler, assets);
195
173
  });
196
174
 
197
175
  compiler.hooks.compilation.tap(pluginName, (compilation) => {
@@ -250,7 +228,3 @@ class WeappPlugin {
250
228
  }
251
229
 
252
230
  module.exports = WeappPlugin;
253
- module.exports._internals = {
254
- injectGlobalModules,
255
- uploadCompilationAssets,
256
- };
package/build/prod.js CHANGED
@@ -3,7 +3,7 @@ const TerserPlugin = require('terser-webpack-plugin');
3
3
  const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
4
4
  const { ENV } = require('./config/constants');
5
5
  const webpackConfig = require('./webpack.config');
6
- const { createCompilerHandler, runCompilerOnce } = require('./utils/createCompilerHandler');
6
+ const { createCompilerHandler } = require('./utils/createCompilerHandler');
7
7
 
8
8
  module.exports = async (opts) => {
9
9
  // 生产环境禁用 filesystem cache,与 dev 同源:output.clean 后复用旧缓存会导致副作用产物漏生成,
@@ -34,5 +34,5 @@ module.exports = async (opts) => {
34
34
  ),
35
35
  );
36
36
 
37
- await runCompilerOnce(compiler, createCompilerHandler('build'));
37
+ compiler.run(createCompilerHandler('build'));
38
38
  };
@@ -48,26 +48,6 @@ function createCompilerHandler(label, { failOnError = label === 'build' } = {})
48
48
  };
49
49
  }
50
50
 
51
- /**
52
- * 运行一次 webpack 编译,并等待异步 done 钩子及 compiler.close 完成。
53
- * 上传任务挂在 done.tapPromise 上,生产命令必须等待这里结束,才能可靠输出上传错误。
54
- */
55
- function runCompilerOnce(compiler, handler) {
56
- return new Promise((resolve, reject) => {
57
- compiler.run((runError, stats) => {
58
- compiler.close((closeError) => {
59
- try {
60
- handler(runError || closeError, stats);
61
- resolve(stats);
62
- } catch (error) {
63
- reject(error);
64
- }
65
- });
66
- });
67
- });
68
- }
69
-
70
51
  module.exports = {
71
52
  createCompilerHandler,
72
- runCompilerOnce,
73
- };
53
+ };
@@ -3,43 +3,27 @@ const path = require('path');
3
3
  const fse = require('fs-extra');
4
4
  const { ASSET_EXT_PATTERN, PATTERNS, SRC_DIR } = require('../config/constants');
5
5
 
6
- function extractStyleUrls(style) {
7
- const urls = [];
8
- const urlPattern = /url\(\s*(?:(['"])(.*?)\1|([^)]*?))\s*\)/gi;
9
- let match;
10
-
11
- // eslint-disable-next-line no-cond-assign
12
- while ((match = urlPattern.exec(style)) !== null) {
13
- const value = (match[2] !== undefined ? match[2] : match[3]).trim();
14
- if (value) {
15
- urls.push(value);
16
- }
17
- }
18
- return urls;
19
- }
20
-
21
6
  function getWxmlAssets(filePath, content) {
22
7
  const resolvePath = (attr, dir) => {
23
- return new Promise((resolve, reject) => {
8
+ return new Promise((resolve) => {
24
9
  const isAbsolutePath = PATTERNS.ABSOLUTE_PATH.test(attr);
25
10
  const request = isAbsolutePath ? attr.replace(PATTERNS.ABSOLUTE_PATH, '') : attr;
26
11
  const resolveContext = isAbsolutePath ? SRC_DIR : dir;
27
12
 
28
13
  this.resolve(resolveContext, request, async (err, result) => {
29
- try {
30
- let res = result;
31
- if (err) {
32
- const fallbackPath = path.resolve(resolveContext, request);
14
+ let res = result;
15
+ if (err) {
16
+ const fallbackPath = path.resolve(resolveContext, request);
33
17
 
34
- if (await fse.pathExists(fallbackPath)) {
35
- res = fallbackPath;
36
- } else {
37
- this.emitError(err);
38
- }
18
+ if (await fse.pathExists(fallbackPath)) {
19
+ res = fallbackPath;
20
+ resolve(res);
21
+ } else {
22
+ this.emitError(err);
23
+ resolve(res);
39
24
  }
25
+ } else {
40
26
  resolve(res);
41
- } catch (error) {
42
- reject(error);
43
27
  }
44
28
  });
45
29
  });
@@ -47,7 +31,8 @@ function getWxmlAssets(filePath, content) {
47
31
  return new Promise((resolve, reject) => {
48
32
  let allAttrs = [];
49
33
  const parser = new htmlparser2.Parser({
50
- onopentag: (name, attributes) => {
34
+ onopentag: async (name, attributes) => {
35
+ const reg = /url\(.*\)/g;
51
36
  const { style } = attributes;
52
37
 
53
38
  allAttrs = allAttrs.concat(
@@ -58,53 +43,60 @@ function getWxmlAssets(filePath, content) {
58
43
  );
59
44
 
60
45
  if (style) {
61
- allAttrs = allAttrs.concat(extractStyleUrls(style));
46
+ const styles = attributes.style.split(';');
47
+ styles.forEach((styleItem) => {
48
+ (styleItem.match(reg) || []).forEach((item) => {
49
+ allAttrs.push(
50
+ item
51
+ .replace(/^(url\(”)/g, '')
52
+ .replace(/^(url\(')/g, '')
53
+ .replace(/^(url\()/g, '')
54
+ .replace(/(“\)|'\)|\))$/g, ''),
55
+ );
56
+ });
57
+ });
62
58
  }
63
59
  },
64
60
  onerror: (err) => {
65
61
  reject(err);
66
62
  },
67
63
  onend: async () => {
68
- try {
69
- const filteredAttrs = allAttrs
70
- .filter((item) => !!item)
71
- .map((item) => item.split('?')[0])
72
- .filter((item) => !/^(?:https?:)?\/\//i.test(item))
73
- .filter((item) => !/{{.*}}/.test(item))
74
- .filter((item) => !/\+.*\+/.test(item));
64
+ const filteredAttrs = allAttrs
65
+ .filter((item) => !!item)
66
+ .map((item) => item.split('?')[0])
67
+ .filter((item) => !/^(http:|https:)/.test(item))
68
+ .filter((item) => !/{{.*}}/.test(item))
69
+ .filter((item) => !/\+.*\+/.test(item));
75
70
 
76
- const assets = filteredAttrs.filter((item) => ASSET_EXT_PATTERN.test(item));
77
- const wxmls = filteredAttrs.filter((item) => PATTERNS.WXS_EXT.test(item) || PATTERNS.WXML_EXT.test(item));
71
+ const assets = filteredAttrs.filter((item) => ASSET_EXT_PATTERN.test(item));
72
+ const wxmls = filteredAttrs.filter((item) => PATTERNS.WXS_EXT.test(item) || PATTERNS.WXML_EXT.test(item));
78
73
 
79
- const assetsImports = [];
80
- const wxmlsImports = [];
74
+ const assetsImports = [];
75
+ const wxmlsImports = [];
81
76
 
82
- // 并行解析 wxml 路径
83
- const wxmlResults = await Promise.all(
84
- wxmls.map(async (attr) => {
85
- const result = await resolvePath(attr, path.parse(filePath).dir);
86
- return result ? [attr, result] : null;
87
- }),
88
- );
89
- for (let index = 0; index < wxmlResults.length; index += 1) {
90
- if (wxmlResults[index]) wxmlsImports.push(wxmlResults[index]);
91
- }
92
-
93
- // 并行解析 asset 路径
94
- const assetResults = await Promise.all(
95
- assets.map(async (attr) => {
96
- const result = await resolvePath(attr, path.parse(filePath).dir);
97
- return result ? [attr, result] : null;
98
- }),
99
- );
100
- for (let index = 0; index < assetResults.length; index += 1) {
101
- if (assetResults[index]) assetsImports.push(assetResults[index]);
102
- }
77
+ // 并行解析 wxml 路径
78
+ const wxmlResults = await Promise.all(
79
+ wxmls.map(async (attr) => {
80
+ const result = await resolvePath(attr, path.parse(filePath).dir);
81
+ return result ? [attr, result] : null;
82
+ }),
83
+ );
84
+ for (let index = 0; index < wxmlResults.length; index += 1) {
85
+ if (wxmlResults[index]) wxmlsImports.push(wxmlResults[index]);
86
+ }
103
87
 
104
- resolve([assetsImports, wxmlsImports]);
105
- } catch (error) {
106
- reject(error);
88
+ // 并行解析 asset 路径
89
+ const assetResults = await Promise.all(
90
+ assets.map(async (attr) => {
91
+ const result = await resolvePath(attr, path.parse(filePath).dir);
92
+ return result ? [attr, result] : null;
93
+ }),
94
+ );
95
+ for (let index = 0; index < assetResults.length; index += 1) {
96
+ if (assetResults[index]) assetsImports.push(assetResults[index]);
107
97
  }
98
+
99
+ resolve([assetsImports, wxmlsImports]);
108
100
  },
109
101
  });
110
102
  parser.write(content);
@@ -114,5 +106,4 @@ function getWxmlAssets(filePath, content) {
114
106
 
115
107
  module.exports = {
116
108
  getWxmlAssets,
117
- extractStyleUrls,
118
109
  };
@@ -16,70 +16,18 @@ const { DIST_DIR } = require('../config/constants');
16
16
 
17
17
  const { obsConfig, ossConfig } = getConfig();
18
18
 
19
- let obsClientPromise;
19
+ let obsClient;
20
20
  let ossClient;
21
-
22
- function getObsResponseError(action, result) {
23
- const commonMsg = result && result.CommonMsg;
24
- const rawStatus = commonMsg && commonMsg.Status;
25
- const status = Number(rawStatus);
26
-
27
- if (
28
- rawStatus === undefined ||
29
- rawStatus === null ||
30
- (typeof rawStatus === 'string' && rawStatus.trim() === '') ||
31
- !Number.isFinite(status)
32
- ) {
33
- return new Error(`OBS ${action} failed: invalid response`);
34
- }
35
- if (status >= 200 && status < 300) {
36
- return null;
37
- }
38
-
39
- const details = [
40
- `status ${status}`,
41
- commonMsg.Code && `code ${commonMsg.Code}`,
42
- commonMsg.Message && commonMsg.Message,
43
- ].filter(Boolean);
44
- return new Error(`OBS ${action} failed with ${details.join(', ')}`);
45
- }
46
-
47
- function createObsCallback(action, resolve, reject) {
48
- return (error, result) => {
49
- if (error) {
50
- reject(error);
51
- return;
52
- }
53
-
54
- const responseError = getObsResponseError(action, result);
55
- if (responseError) {
56
- reject(responseError);
57
- } else {
58
- resolve(result);
59
- }
60
- };
61
- }
62
-
63
- async function initializeObsClient(Client = OBSClient, config = obsConfig) {
64
- const client = new Client({ ...config });
65
-
66
- // esdk-obs-nodejs 3.26.x 的构造函数不会等待异步 initFactory()。
67
- // 静态 AK/SK 初始化会在下一个微任务完成,立即调用 API 时 signatureContext 仍为 null。
68
- await Promise.resolve();
69
- if (!client.util || !client.util.signatureContext) {
70
- throw new Error('OBS client initialization failed');
71
- }
72
- return client;
73
- }
21
+ let progress;
22
+ let uploadQueue = {};
74
23
 
75
24
  function getObsClient() {
76
- if (!obsClientPromise) {
77
- obsClientPromise = initializeObsClient().catch((error) => {
78
- obsClientPromise = undefined;
79
- throw error;
25
+ if (!obsClient) {
26
+ obsClient = new OBSClient({
27
+ ...obsConfig,
80
28
  });
81
29
  }
82
- return obsClientPromise;
30
+ return obsClient;
83
31
  }
84
32
 
85
33
  function getOssClient() {
@@ -102,28 +50,42 @@ function getOssStat(file) {
102
50
  return getOssClient().head(compatiblePath(path.join(ossConfig.dir, path.relative(DIST_DIR, file))));
103
51
  }
104
52
 
105
- async function doObsUpload(file) {
106
- const client = await getObsClient();
53
+ function doObsUpload(file) {
107
54
  return new Promise((resolve, reject) => {
108
- client.putObject(
55
+ getObsClient().putObject(
109
56
  {
110
57
  Bucket: obsConfig.bucket,
111
58
  Key: compatiblePath(path.join(obsConfig.dir, path.relative(DIST_DIR, file))),
112
59
  SourceFile: file,
113
60
  },
114
- createObsCallback('upload', resolve, reject),
61
+ (err, result) => {
62
+ if (err) {
63
+ reject(err);
64
+ } else if (result.CommonMsg.Status >= 300) {
65
+ reject(new Error(`OBS upload failed with status ${result.CommonMsg.Status}`));
66
+ } else {
67
+ resolve(result);
68
+ }
69
+ },
115
70
  );
116
71
  });
117
72
  }
118
73
  async function getObsStat(file) {
119
- const client = await getObsClient();
120
74
  return new Promise((resolve, reject) => {
121
- client.getObjectMetadata(
75
+ getObsClient().getObjectMetadata(
122
76
  {
123
77
  Bucket: obsConfig.bucket,
124
78
  Key: compatiblePath(path.join(obsConfig.dir, path.relative(DIST_DIR, file))),
125
79
  },
126
- createObsCallback('metadata request', resolve, reject),
80
+ (err, result) => {
81
+ if (err) {
82
+ reject(err);
83
+ } else if (result.CommonMsg.Status < 300) {
84
+ resolve(result);
85
+ } else {
86
+ reject(result);
87
+ }
88
+ },
127
89
  );
128
90
  });
129
91
  }
@@ -148,64 +110,77 @@ function doUpload(file) {
148
110
  return doObsUpload(file);
149
111
  }
150
112
 
151
- async function uploadFile(file) {
152
- try {
153
- await getStat(file);
154
- } catch (error) {
155
- await doUpload(file);
113
+ function updateProgress() {
114
+ const completed =
115
+ Object.entries(uploadQueue).filter((item) => item[1] === 'completed').length + 1;
116
+ const total = Object.keys(uploadQueue).length;
117
+
118
+ if (!progress) {
119
+ progress = new Progress('uploading assets [:bar] :current/:total', {
120
+ total,
121
+ width: 40,
122
+ clear: true,
123
+ });
124
+ }
125
+
126
+ progress.tick();
127
+
128
+ if (completed === total) {
129
+ progress.tick({
130
+ current: total,
131
+ });
132
+ progress = null;
133
+ uploadQueue = {};
156
134
  }
157
- setStorage(file, true);
135
+
136
+ return `[${completed}/${total}]`;
158
137
  }
159
138
 
160
- async function uploadFiles(files, task = uploadFile) {
161
- const failures = [];
162
- const progress = new Progress('uploading assets [:bar] :current/:total', {
163
- total: files.length,
164
- width: 40,
165
- clear: true,
166
- });
139
+ async function checkUpload() {
140
+ const files = Object.entries(uploadQueue)
141
+ .filter((item) => item[1] === false)
142
+ .splice(0, 10);
167
143
 
168
- for (let index = 0; index < files.length; index += 10) {
169
- const batch = files.slice(index, index + 10);
144
+ if (files && files.length) {
170
145
  await Promise.all(
171
- batch.map(async (file) => {
146
+ files.map(async (file) => {
147
+ uploadQueue[file[0]] = 'uploading';
172
148
  try {
173
- await task(file);
149
+ await getStat(file[0]);
150
+ setStorage(file[0], true);
151
+ updateProgress();
174
152
  } catch (error) {
175
- failures.push({ file, error });
176
- } finally {
177
- progress.tick();
153
+ try {
154
+ await doUpload(file[0]);
155
+ setStorage(file[0], true);
156
+ } catch (uploadError) {
157
+ console.error(`Failed to upload ${file[0]}:`, uploadError);
158
+ }
159
+ updateProgress();
178
160
  }
161
+ uploadQueue[file[0]] = 'completed';
179
162
  }),
180
163
  );
181
- }
182
164
 
183
- if (failures.length > 0) {
184
- const details = failures
185
- .map(({ file, error }) => `${file}: ${error && error.message ? error.message : error}`)
186
- .join('\n');
187
- const error = new Error(`Failed to upload ${failures.length} asset(s):\n${details}`);
188
- error.failures = failures;
189
- throw error;
165
+ checkUpload();
190
166
  }
191
167
  }
192
168
 
193
- async function addToUploadQueue(assets) {
194
- if ((!obsConfig && !ossConfig) || assets.length === 0) {
169
+ function addToUploadQueue(assets) {
170
+ if (!obsConfig && !ossConfig) {
171
+ console.warn('请配置obsConfig 或 ossConfig,否则无法上传文件到obs 或 oss');
195
172
  return;
196
173
  }
197
174
 
198
- const files = Array.from(new Set(assets.map((asset) => path.resolve(DIST_DIR, asset))));
199
- await uploadFiles(files);
175
+ assets.forEach((asset) => {
176
+ const file = path.resolve(DIST_DIR, asset);
177
+ if (uploadQueue[file] === undefined) {
178
+ uploadQueue[file] = false;
179
+ }
180
+ });
181
+ checkUpload();
200
182
  }
201
183
 
202
184
  module.exports = {
203
185
  addToUploadQueue,
204
- _internals: {
205
- createObsCallback,
206
- getObsResponseError,
207
- initializeObsClient,
208
- uploadFile,
209
- uploadFiles,
210
- },
211
186
  };
@@ -180,13 +180,13 @@ module.exports = async (options, { analyzer, quiet } = {}) => {
180
180
  loader: MiniCssExtractPlugin.loader,
181
181
  },
182
182
  {
183
- loader: require.resolve('css-loader'),
183
+ loader: 'css-loader',
184
184
  options: {
185
185
  importLoaders: use.length + 1,
186
186
  },
187
187
  },
188
188
  {
189
- loader: require.resolve('postcss-loader'),
189
+ loader: 'postcss-loader',
190
190
  options: {
191
191
  postcssOptions: {
192
192
  plugins: [
@@ -239,16 +239,14 @@ module.exports = async (options, { analyzer, quiet } = {}) => {
239
239
  });
240
240
  });
241
241
 
242
- if (patterns.length > 0) {
243
- plugins.push(
244
- new CopyPlugin({
245
- patterns,
246
- options: {
247
- concurrency: 100,
248
- },
249
- }),
250
- );
251
- }
242
+ plugins.push(
243
+ new CopyPlugin({
244
+ patterns,
245
+ options: {
246
+ concurrency: 100,
247
+ },
248
+ }),
249
+ );
252
250
 
253
251
  return merge(
254
252
  {
@@ -277,12 +275,12 @@ module.exports = async (options, { analyzer, quiet } = {}) => {
277
275
  test: ASSET_EXT_PATTERN,
278
276
  use: [
279
277
  {
280
- loader: require.resolve('url-loader'),
278
+ loader: 'url-loader',
281
279
  options: {
282
280
  limit: 0,
283
281
  esModule: false,
284
282
  fallback: {
285
- loader: require.resolve('file-loader'),
283
+ loader: 'file-loader',
286
284
  options: {
287
285
  name: `${ASSETS_DIR}/[name].[hash].[ext]`,
288
286
  },
@@ -303,7 +301,7 @@ module.exports = async (options, { analyzer, quiet } = {}) => {
303
301
  ...getCssLoader({
304
302
  use: [
305
303
  {
306
- loader: require.resolve('less-loader'),
304
+ loader: 'less-loader',
307
305
  options: {
308
306
  lessOptions() {
309
307
  return {
@@ -321,7 +319,7 @@ module.exports = async (options, { analyzer, quiet } = {}) => {
321
319
  type: 'javascript/auto',
322
320
  use: [
323
321
  {
324
- loader: require.resolve('file-loader'),
322
+ loader: 'file-loader',
325
323
  options: {
326
324
  name: assetsName,
327
325
  },
@@ -333,7 +331,7 @@ module.exports = async (options, { analyzer, quiet } = {}) => {
333
331
  type: 'javascript/auto',
334
332
  use: [
335
333
  {
336
- loader: require.resolve('file-loader'),
334
+ loader: 'file-loader',
337
335
  options: {
338
336
  name: assetsName,
339
337
  },
@@ -347,7 +345,7 @@ module.exports = async (options, { analyzer, quiet } = {}) => {
347
345
  test: /\.(wxml)$/i,
348
346
  use: [
349
347
  {
350
- loader: require.resolve('file-loader'),
348
+ loader: 'file-loader',
351
349
  options: {
352
350
  name: assetsName,
353
351
  },
package/index.js CHANGED
@@ -1,14 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // 高版本 Node.js (>=17) 兼容 OpenSSL 3.0
4
+ const nodeMajor = process.versions.node.split('.')[0];
5
+ if (nodeMajor >= 17) {
6
+ process.env.NODE_OPTIONS = `${process.env.NODE_OPTIONS || ''} --openssl-legacy-provider`.trim();
7
+ }
8
+
3
9
  const { program } = require('commander');
4
10
  const path = require('path');
5
11
  const fs = require('fs');
12
+ const dev = require('./build/dev');
13
+ const prod = require('./build/prod');
6
14
  const { setBuildEnv } = require('./build/utils/buildEnv');
7
15
  const { ENV } = require('./build/config/constants');
8
16
 
9
17
  const { version } = JSON.parse(
10
18
  fs.readFileSync(path.resolve(__dirname, './package.json')).toString(),
11
19
  );
20
+ const compiler = {
21
+ dev,
22
+ prod,
23
+ };
24
+
12
25
  program.version(version);
13
26
  program.option('-a, --analyzer', 'webpack-bundle-analyzer');
14
27
  program.option('-s, --simulation', 'process.env.BUILD_ENV = simulation');
@@ -16,27 +29,25 @@ program.option('-d, --development', 'process.env.BUILD_ENV = development');
16
29
  program.option('-p, --production', 'process.env.BUILD_ENV = production');
17
30
  program.option('-q, --quiet', '安静模式,打印减少');
18
31
 
19
- program.command('dev').action(async () => {
32
+ program.command('dev').action(() => {
20
33
  setBuildEnv({
21
34
  mode: ENV.DEV,
22
35
  ...program.opts(),
23
36
  });
24
- // 延迟加载构建模块,保证 --help/--version 不依赖业务项目配置。
25
- // eslint-disable-next-line global-require
26
- const dev = require('./build/dev');
27
- await dev(program.opts());
37
+ compiler.dev(program.opts()).catch((err) => {
38
+ console.error(err);
39
+ process.exitCode = 1;
40
+ });
28
41
  });
29
- program.command('build').action(async () => {
42
+ program.command('build').action(() => {
30
43
  setBuildEnv({
31
44
  mode: ENV.PROD,
32
45
  ...program.opts(),
33
46
  });
34
- // eslint-disable-next-line global-require
35
- const prod = require('./build/prod');
36
- await prod(program.opts());
47
+ compiler.prod(program.opts()).catch((err) => {
48
+ console.error(err);
49
+ process.exitCode = 1;
50
+ });
37
51
  });
38
52
 
39
- program.parseAsync(process.argv).catch((err) => {
40
- console.error(err);
41
- process.exitCode = 1;
42
- });
53
+ program.parse(process.argv);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hw-weapp-compiler",
3
- "version": "1.0.21",
4
- "description": "基于 webpack5 的小程序构建工具,兼容 Node.js 18~24",
3
+ "version": "1.0.23",
4
+ "description": "基于 webpack5 的小程序构建工具,兼容 Node.js 14~24",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "hw-weapp": "index.js"
@@ -12,7 +12,7 @@
12
12
  "registry": "https://registry.npmjs.org"
13
13
  },
14
14
  "engines": {
15
- "node": ">=18"
15
+ "node": ">=14.17.1"
16
16
  },
17
17
  "dependencies": {
18
18
  "@swc/core": "^1.15.43",
@@ -30,8 +30,7 @@
30
30
  "file-loader": "^6.2.0",
31
31
  "fs-extra": "^9.1.0",
32
32
  "htmlparser2": "^6.0.1",
33
- "isobject": "3.0.1",
34
- "less": "4.6.7",
33
+ "less": "^4.1.1",
35
34
  "less-loader": "^8.0.0",
36
35
  "loader-utils": "^2.0.0",
37
36
  "mini-css-extract-plugin": "^1.6.0",
@@ -42,7 +41,7 @@
42
41
  "terser-webpack-plugin": "^5.6.1",
43
42
  "throttle-debounce": "^3.0.1",
44
43
  "url-loader": "^4.1.1",
45
- "webpack": "5.108.4",
44
+ "webpack": "^5.61.0",
46
45
  "webpack-bundle-analyzer": "^4.4.0",
47
46
  "webpack-merge": "^5.7.3",
48
47
  "webpack-sources": "^2.2.0"
@@ -53,7 +52,6 @@
53
52
  "scripts": {
54
53
  "build": "node ./index.js build",
55
54
  "dev": "nodemon --inspect --trace-deprecation --watch build ./index.js dev",
56
- "simulation": "node ./index.js build -s",
57
- "test": "node --test"
55
+ "simulation": "node ./index.js build -s"
58
56
  }
59
57
  }
@@ -1,49 +0,0 @@
1
- const fs = require('node:fs');
2
- const path = require('node:path');
3
- const vm = require('node:vm');
4
-
5
- function executeMiniProgramDist(dist, entries = ['app.js', 'pages/index/index.js']) {
6
- const sandbox = {
7
- console,
8
- appConfig: null,
9
- pageConfig: null,
10
- };
11
- sandbox.global = sandbox;
12
- sandbox.App = (config) => {
13
- sandbox.appConfig = config;
14
- };
15
- sandbox.Page = (config) => {
16
- sandbox.pageConfig = config;
17
- };
18
-
19
- const context = vm.createContext(sandbox);
20
- const moduleCache = new Map();
21
-
22
- const load = (request, from = 'app.js') => {
23
- let filename = path.posix.normalize(path.posix.join(path.posix.dirname(from), request));
24
- if (!path.posix.extname(filename)) {
25
- filename += '.js';
26
- }
27
- if (moduleCache.has(filename)) {
28
- return moduleCache.get(filename).exports;
29
- }
30
-
31
- const loadedModule = { exports: {} };
32
- moduleCache.set(filename, loadedModule);
33
- const source = fs.readFileSync(path.join(dist, ...filename.split('/')), 'utf8');
34
- const wrapper = vm.runInContext(
35
- `(function (require, module, exports) { ${source}\n })`,
36
- context,
37
- { filename },
38
- );
39
- wrapper((child) => load(child, filename), loadedModule, loadedModule.exports);
40
- return loadedModule.exports;
41
- };
42
-
43
- for (const entry of entries) {
44
- load(`./${entry}`);
45
- }
46
- return sandbox;
47
- }
48
-
49
- module.exports = { executeMiniProgramDist };
@@ -1,25 +0,0 @@
1
- const fs = require('node:fs');
2
- const os = require('node:os');
3
- const path = require('node:path');
4
-
5
- function createTempProject(prefix = 'hw-weapp-test-') {
6
- const root = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
7
-
8
- return {
9
- root,
10
- resolve(...segments) {
11
- return path.join(root, ...segments);
12
- },
13
- write(relativePath, content) {
14
- const filename = path.join(root, relativePath);
15
- fs.mkdirSync(path.dirname(filename), { recursive: true });
16
- fs.writeFileSync(filename, content);
17
- return filename;
18
- },
19
- cleanup() {
20
- fs.rmSync(root, { recursive: true, force: true });
21
- },
22
- };
23
- }
24
-
25
- module.exports = { createTempProject };
@@ -1,80 +0,0 @@
1
- const Module = require('node:module');
2
- const webpack = require('webpack');
3
-
4
- function runCompiler(config) {
5
- const compiler = webpack(config);
6
-
7
- return new Promise((resolve, reject) => {
8
- compiler.run((runError, stats) => {
9
- compiler.close((closeError) => {
10
- if (runError || closeError) {
11
- reject(runError || closeError);
12
- } else {
13
- resolve(stats);
14
- }
15
- });
16
- });
17
- });
18
- }
19
-
20
- function installMissingLoaderFallbacks(fallbacks) {
21
- const originalResolveFilename = Module._resolveFilename;
22
- const missingFallbacks = new Map();
23
-
24
- for (const [request, fallback] of Object.entries(fallbacks)) {
25
- try {
26
- originalResolveFilename.call(Module, request, module, false);
27
- } catch (error) {
28
- missingFallbacks.set(request, fallback);
29
- }
30
- }
31
-
32
- Module._resolveFilename = function resolveFilename(request, parent, isMain, options) {
33
- if (missingFallbacks.has(request)) {
34
- return missingFallbacks.get(request);
35
- }
36
- return originalResolveFilename.call(this, request, parent, isMain, options);
37
- };
38
-
39
- return () => {
40
- Module._resolveFilename = originalResolveFilename;
41
- };
42
- }
43
-
44
- function createAssetLoaderFallbacks(project) {
45
- const fileLoader = project.write(
46
- 'test-file-loader.js',
47
- [
48
- "const path = require('path');",
49
- 'module.exports = function loader(content) {',
50
- " const name = path.relative(this.rootContext, this.resourcePath).replace(/\\\\/g, '/');",
51
- ' this.emitFile(name, content);',
52
- ' return `module.exports = ${JSON.stringify(name)};`;',
53
- '};',
54
- 'module.exports.raw = true;',
55
- ].join('\n'),
56
- );
57
- const urlLoader = project.write(
58
- 'test-url-loader.js',
59
- [
60
- "const crypto = require('crypto');",
61
- "const path = require('path');",
62
- 'module.exports = function loader(content) {',
63
- " const hash = crypto.createHash('sha256').update(content).digest('hex').slice(0, 16);",
64
- ' const info = path.parse(this.resourcePath);',
65
- ' const name = `assets/${info.name}.${hash}${info.ext}`;',
66
- ' this.emitFile(name, content);',
67
- ' return `module.exports = ${JSON.stringify(name)};`;',
68
- '};',
69
- 'module.exports.raw = true;',
70
- ].join('\n'),
71
- );
72
-
73
- return { 'file-loader': fileLoader, 'url-loader': urlLoader };
74
- }
75
-
76
- module.exports = {
77
- createAssetLoaderFallbacks,
78
- installMissingLoaderFallbacks,
79
- runCompiler,
80
- };