hw-weapp-compiler 1.0.18 → 1.0.20

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
@@ -14,6 +14,8 @@
14
14
  npm install hw-weapp-compiler@1 -D
15
15
  ```
16
16
 
17
+ 升级版本后如果出现 `Cannot find module`,请删除业务项目中旧的安装产物并重新执行 `npm install`,确保传递依赖与 lockfile 同步。
18
+
17
19
  在业务项目的 `package.json` 中添加:
18
20
 
19
21
  ```json
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);
@@ -19,6 +19,47 @@ const { obsConfig, ossConfig } = getConfig();
19
19
  let obsClient;
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
+
22
63
  function getObsClient() {
23
64
  if (!obsClient) {
24
65
  obsClient = new OBSClient({
@@ -56,15 +97,7 @@ function doObsUpload(file) {
56
97
  Key: compatiblePath(path.join(obsConfig.dir, path.relative(DIST_DIR, file))),
57
98
  SourceFile: file,
58
99
  },
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
- },
100
+ createObsCallback('upload', resolve, reject),
68
101
  );
69
102
  });
70
103
  }
@@ -75,15 +108,7 @@ async function getObsStat(file) {
75
108
  Bucket: obsConfig.bucket,
76
109
  Key: compatiblePath(path.join(obsConfig.dir, path.relative(DIST_DIR, file))),
77
110
  },
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
- },
111
+ createObsCallback('metadata request', resolve, reject),
87
112
  );
88
113
  });
89
114
  }
@@ -162,6 +187,8 @@ async function addToUploadQueue(assets) {
162
187
  module.exports = {
163
188
  addToUploadQueue,
164
189
  _internals: {
190
+ createObsCallback,
191
+ getObsResponseError,
165
192
  uploadFile,
166
193
  uploadFiles,
167
194
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hw-weapp-compiler",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "description": "基于 webpack5 的小程序构建工具,兼容 Node.js 18~24",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -30,6 +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",
33
34
  "less": "4.6.7",
34
35
  "less-loader": "^8.0.0",
35
36
  "loader-utils": "^2.0.0",
@@ -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
+ };