hw-weapp-compiler 1.0.19 → 1.0.21

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/build/dev.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const webpack = require('webpack');
2
+ const chalk = require('chalk');
2
3
  const { ENV } = require('./config/constants');
3
4
  const webpackConfig = require('./webpack.config');
4
5
  const { createCompilerHandler } = require('./utils/createCompilerHandler');
@@ -20,7 +21,7 @@ module.exports = async (opts) => {
20
21
  ),
21
22
  );
22
23
 
23
- compiler.watch(
24
+ const watching = compiler.watch(
24
25
  {
25
26
  aggregateTimeout: 300,
26
27
  poll: false,
@@ -28,4 +29,9 @@ module.exports = async (opts) => {
28
29
  },
29
30
  createCompilerHandler('dev'),
30
31
  );
32
+
33
+ if (!opts || opts.quiet !== true) {
34
+ console.log(chalk.cyan('dev 正在监听文件变化'));
35
+ }
36
+ return watching;
31
37
  };
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 } = require('./utils/createCompilerHandler');
6
+ const { createCompilerHandler, runCompilerOnce } = 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
- compiler.run(createCompilerHandler('build'));
37
+ await runCompilerOnce(compiler, createCompilerHandler('build'));
38
38
  };
@@ -48,6 +48,26 @@ 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
+
51
70
  module.exports = {
52
71
  createCompilerHandler,
53
- };
72
+ runCompilerOnce,
73
+ };
@@ -20,25 +20,26 @@ function extractStyleUrls(style) {
20
20
 
21
21
  function getWxmlAssets(filePath, content) {
22
22
  const resolvePath = (attr, dir) => {
23
- return new Promise((resolve) => {
23
+ return new Promise((resolve, reject) => {
24
24
  const isAbsolutePath = PATTERNS.ABSOLUTE_PATH.test(attr);
25
25
  const request = isAbsolutePath ? attr.replace(PATTERNS.ABSOLUTE_PATH, '') : attr;
26
26
  const resolveContext = isAbsolutePath ? SRC_DIR : dir;
27
27
 
28
28
  this.resolve(resolveContext, request, async (err, result) => {
29
- let res = result;
30
- if (err) {
31
- const fallbackPath = path.resolve(resolveContext, request);
29
+ try {
30
+ let res = result;
31
+ if (err) {
32
+ const fallbackPath = path.resolve(resolveContext, request);
32
33
 
33
- if (await fse.pathExists(fallbackPath)) {
34
- res = fallbackPath;
35
- resolve(res);
36
- } else {
37
- this.emitError(err);
38
- resolve(res);
34
+ if (await fse.pathExists(fallbackPath)) {
35
+ res = fallbackPath;
36
+ } else {
37
+ this.emitError(err);
38
+ }
39
39
  }
40
- } else {
41
40
  resolve(res);
41
+ } catch (error) {
42
+ reject(error);
42
43
  }
43
44
  });
44
45
  });
@@ -46,7 +47,7 @@ function getWxmlAssets(filePath, content) {
46
47
  return new Promise((resolve, reject) => {
47
48
  let allAttrs = [];
48
49
  const parser = new htmlparser2.Parser({
49
- onopentag: async (name, attributes) => {
50
+ onopentag: (name, attributes) => {
50
51
  const { style } = attributes;
51
52
 
52
53
  allAttrs = allAttrs.concat(
@@ -64,42 +65,46 @@ function getWxmlAssets(filePath, content) {
64
65
  reject(err);
65
66
  },
66
67
  onend: async () => {
67
- const filteredAttrs = allAttrs
68
- .filter((item) => !!item)
69
- .map((item) => item.split('?')[0])
70
- .filter((item) => !/^(?:https?:)?\/\//i.test(item))
71
- .filter((item) => !/{{.*}}/.test(item))
72
- .filter((item) => !/\+.*\+/.test(item));
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));
73
75
 
74
- const assets = filteredAttrs.filter((item) => ASSET_EXT_PATTERN.test(item));
75
- const wxmls = filteredAttrs.filter((item) => PATTERNS.WXS_EXT.test(item) || PATTERNS.WXML_EXT.test(item));
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));
76
78
 
77
- const assetsImports = [];
78
- const wxmlsImports = [];
79
+ const assetsImports = [];
80
+ const wxmlsImports = [];
79
81
 
80
- // 并行解析 wxml 路径
81
- const wxmlResults = await Promise.all(
82
- wxmls.map(async (attr) => {
83
- const result = await resolvePath(attr, path.parse(filePath).dir);
84
- return result ? [attr, result] : null;
85
- }),
86
- );
87
- for (let index = 0; index < wxmlResults.length; index += 1) {
88
- if (wxmlResults[index]) wxmlsImports.push(wxmlResults[index]);
89
- }
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
+ }
90
92
 
91
- // 并行解析 asset 路径
92
- const assetResults = await Promise.all(
93
- assets.map(async (attr) => {
94
- const result = await resolvePath(attr, path.parse(filePath).dir);
95
- return result ? [attr, result] : null;
96
- }),
97
- );
98
- for (let index = 0; index < assetResults.length; index += 1) {
99
- if (assetResults[index]) assetsImports.push(assetResults[index]);
100
- }
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
+ }
101
103
 
102
- resolve([assetsImports, wxmlsImports]);
104
+ resolve([assetsImports, wxmlsImports]);
105
+ } catch (error) {
106
+ reject(error);
107
+ }
103
108
  },
104
109
  });
105
110
  parser.write(content);
@@ -16,16 +16,70 @@ const { DIST_DIR } = require('../config/constants');
16
16
 
17
17
  const { obsConfig, ossConfig } = getConfig();
18
18
 
19
- let obsClient;
19
+ let obsClientPromise;
20
20
  let ossClient;
21
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
+ }
74
+
22
75
  function getObsClient() {
23
- if (!obsClient) {
24
- obsClient = new OBSClient({
25
- ...obsConfig,
76
+ if (!obsClientPromise) {
77
+ obsClientPromise = initializeObsClient().catch((error) => {
78
+ obsClientPromise = undefined;
79
+ throw error;
26
80
  });
27
81
  }
28
- return obsClient;
82
+ return obsClientPromise;
29
83
  }
30
84
 
31
85
  function getOssClient() {
@@ -48,42 +102,28 @@ function getOssStat(file) {
48
102
  return getOssClient().head(compatiblePath(path.join(ossConfig.dir, path.relative(DIST_DIR, file))));
49
103
  }
50
104
 
51
- function doObsUpload(file) {
105
+ async function doObsUpload(file) {
106
+ const client = await getObsClient();
52
107
  return new Promise((resolve, reject) => {
53
- getObsClient().putObject(
108
+ client.putObject(
54
109
  {
55
110
  Bucket: obsConfig.bucket,
56
111
  Key: compatiblePath(path.join(obsConfig.dir, path.relative(DIST_DIR, file))),
57
112
  SourceFile: file,
58
113
  },
59
- (err, result) => {
60
- if (err) {
61
- reject(err);
62
- } else if (result.CommonMsg.Status >= 300) {
63
- reject(new Error(`OBS upload failed with status ${result.CommonMsg.Status}`));
64
- } else {
65
- resolve(result);
66
- }
67
- },
114
+ createObsCallback('upload', resolve, reject),
68
115
  );
69
116
  });
70
117
  }
71
118
  async function getObsStat(file) {
119
+ const client = await getObsClient();
72
120
  return new Promise((resolve, reject) => {
73
- getObsClient().getObjectMetadata(
121
+ client.getObjectMetadata(
74
122
  {
75
123
  Bucket: obsConfig.bucket,
76
124
  Key: compatiblePath(path.join(obsConfig.dir, path.relative(DIST_DIR, file))),
77
125
  },
78
- (err, result) => {
79
- if (err) {
80
- reject(err);
81
- } else if (result.CommonMsg.Status < 300) {
82
- resolve(result);
83
- } else {
84
- reject(result);
85
- }
86
- },
126
+ createObsCallback('metadata request', resolve, reject),
87
127
  );
88
128
  });
89
129
  }
@@ -162,6 +202,9 @@ async function addToUploadQueue(assets) {
162
202
  module.exports = {
163
203
  addToUploadQueue,
164
204
  _internals: {
205
+ createObsCallback,
206
+ getObsResponseError,
207
+ initializeObsClient,
165
208
  uploadFile,
166
209
  uploadFiles,
167
210
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hw-weapp-compiler",
3
- "version": "1.0.19",
3
+ "version": "1.0.21",
4
4
  "description": "基于 webpack5 的小程序构建工具,兼容 Node.js 18~24",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -0,0 +1,49 @@
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 };
@@ -0,0 +1,25 @@
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 };
@@ -0,0 +1,80 @@
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
+ };