hw-weapp-compiler 1.0.22 → 1.0.24

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,80 @@
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
+ // 可选:单次 SDK 请求超时、重试次数,以及编译器无进展兜底超时(秒)
57
+ timeout: 15,
58
+ max_retry_count: 1,
59
+ operation_timeout: 35,
60
+ },
61
+ ossConfig: {
62
+ region: '<Your region>',
63
+ accessKeyId: '<Your AccessKeyId>',
64
+ accessKeySecret: '<Your AccessKeySecret>',
65
+ bucket: '<Your Bucket>',
66
+ dir: 'weapp',
67
+ },
97
68
  };
98
69
  ```
99
70
 
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` 中忽略本地产物:
71
+ `project.config.json` 中忽略构建产物:
109
72
 
110
73
  ```json
111
- {
112
- "packOptions": {
113
- "ignore": [
114
- { "type": "folder", "value": "assets" },
115
- { "type": "regexp", "value": "\\.map$" }
116
- ]
117
- }
74
+ "packOptions": {
75
+ "ignore": [
76
+ { "type": "folder", "value": "assets" },
77
+ { "type": "regexp", "value": "\\.map$" }
78
+ ]
118
79
  }
119
80
  ```
@@ -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
  };
@@ -34,14 +34,12 @@ module.exports = async function loader(source) {
34
34
  return src ? [attr, src] : null;
35
35
  }),
36
36
  );
37
- // 路径可能互为后缀(如 a.png icons/a.png),必须先替换较长路径,
38
- // 否则短路径会破坏尚未处理的长路径。
39
- const resolvedAssets = assetResults
40
- .filter(Boolean)
41
- .sort(([left], [right]) => right.length - left.length);
42
- for (let index = 0; index < resolvedAssets.length; index += 1) {
43
- const [attr, src] = resolvedAssets[index];
44
- content = content.split(attr).join(withPublicPath(src));
37
+ for (let index = 0; index < assetResults.length; index += 1) {
38
+ const result = assetResults[index];
39
+ if (result) {
40
+ const [attr, src] = result;
41
+ content = content.split(attr).join(withPublicPath(src));
42
+ }
45
43
  }
46
44
 
47
45
  // wxml文件 - 并行 loadModule
@@ -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
  );
@@ -116,13 +95,13 @@ function injectGlobalModules(content, assetName, flags) {
116
95
  'Function("r", "regeneratorRuntime = r")(runtime);',
117
96
  'global.regeneratorRuntime = runtime',
118
97
  );
119
- if (!result.includes("require('./commons.js');") && flags.hasCommonJs) {
98
+ if (!/require('\.\/commons\.js')/.test(result) && flags.hasCommonJs) {
120
99
  result = `require('./commons.js');\n${result}`;
121
100
  }
122
- if (!result.includes("require('./vendors.js');") && flags.hasVendorJs) {
101
+ if (!/require('\.\/vendors\.js')/.test(result) && flags.hasVendorJs) {
123
102
  result = `require('./vendors.js');\n${result}`;
124
103
  }
125
- if (!result.includes("require('./runtime.js');") && flags.hasRuntimeJs) {
104
+ if (!/require('\.\/runtime\.js')/.test(result) && flags.hasRuntimeJs) {
126
105
  result = `require('./runtime.js');\n${result}`;
127
106
  }
128
107
  }
@@ -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 后复用旧缓存会导致副作用产物漏生成,
@@ -16,7 +16,7 @@ module.exports = async (opts) => {
16
16
  devtool: false,
17
17
  output: {
18
18
  clean: {
19
- keep: /^weapp\.env\.json$/,
19
+ keep: /^(project\.config\.json|weapp\.env\.json)$/,
20
20
  },
21
21
  },
22
22
  optimization: {
@@ -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
  };
@@ -2,7 +2,7 @@ const path = require('path');
2
2
  const fse = require('fs-extra');
3
3
  const { DIST_DIR } = require('../config/constants');
4
4
 
5
- const KEEP_FILES = new Set(['weapp.env.json']);
5
+ const KEEP_FILES = new Set(['project.config.json', 'weapp.env.json']);
6
6
 
7
7
  /**
8
8
  * 开发模式启动时清理 dist 旧产物。
@@ -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,55 +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
- // http(s)、cloud、wxfile、data 等带协议资源均由小程序运行时处理,
73
- // 只有本地相对路径、根路径和 webpack alias 才进入 resolver。
74
- .filter((item) => !/^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(item))
75
- .filter((item) => !/{{.*}}/.test(item))
76
- .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));
77
70
 
78
- const assets = filteredAttrs.filter((item) => ASSET_EXT_PATTERN.test(item));
79
- 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));
80
73
 
81
- const assetsImports = [];
82
- const wxmlsImports = [];
74
+ const assetsImports = [];
75
+ const wxmlsImports = [];
83
76
 
84
- // 并行解析 wxml 路径
85
- const wxmlResults = await Promise.all(
86
- wxmls.map(async (attr) => {
87
- const result = await resolvePath(attr, path.parse(filePath).dir);
88
- return result ? [attr, result] : null;
89
- }),
90
- );
91
- for (let index = 0; index < wxmlResults.length; index += 1) {
92
- if (wxmlResults[index]) wxmlsImports.push(wxmlResults[index]);
93
- }
94
-
95
- // 并行解析 asset 路径
96
- const assetResults = await Promise.all(
97
- assets.map(async (attr) => {
98
- const result = await resolvePath(attr, path.parse(filePath).dir);
99
- return result ? [attr, result] : null;
100
- }),
101
- );
102
- for (let index = 0; index < assetResults.length; index += 1) {
103
- if (assetResults[index]) assetsImports.push(assetResults[index]);
104
- }
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
+ }
105
87
 
106
- resolve([assetsImports, wxmlsImports]);
107
- } catch (error) {
108
- 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]);
109
97
  }
98
+
99
+ resolve([assetsImports, wxmlsImports]);
110
100
  },
111
101
  });
112
102
  parser.write(content);
@@ -116,5 +106,4 @@ function getWxmlAssets(filePath, content) {
116
106
 
117
107
  module.exports = {
118
108
  getWxmlAssets,
119
- extractStyleUrls,
120
109
  };
@@ -4,40 +4,18 @@ const { debounce } = require('throttle-debounce');
4
4
 
5
5
  const tempDir = path.resolve(process.cwd(), '.temp');
6
6
  const uploadJsonDir = path.join(tempDir, 'upload.json');
7
- const uploadJsonTempDir = `${uploadJsonDir}.${process.pid}.tmp`;
8
7
  let uploadJson = {};
8
+ const writeUploadJsonDebounce = debounce(300, () => {
9
+ fs.writeJSONSync(uploadJsonDir, uploadJson);
10
+ });
9
11
 
10
- function writeUploadJson() {
11
- try {
12
- fs.writeJSONSync(uploadJsonTempDir, uploadJson);
13
- fs.renameSync(uploadJsonTempDir, uploadJsonDir);
14
- } catch (error) {
15
- try {
16
- fs.removeSync(uploadJsonTempDir);
17
- } catch (cleanupError) {
18
- console.warn('清理上传缓存临时文件失败:', cleanupError);
19
- }
20
- console.warn('写入上传缓存失败,本次构建将继续:', error);
21
- }
12
+ if (!fs.existsSync(tempDir)) {
13
+ fs.mkdirSync(tempDir);
22
14
  }
23
-
24
- const writeUploadJsonDebounce = debounce(300, writeUploadJson);
25
-
26
- fs.ensureDirSync(tempDir);
27
15
  if (!fs.existsSync(uploadJsonDir)) {
28
- writeUploadJson();
16
+ fs.writeJSONSync(uploadJsonDir, uploadJson);
29
17
  } else {
30
- try {
31
- const stored = fs.readJSONSync(uploadJsonDir);
32
- if (!stored || Array.isArray(stored) || typeof stored !== 'object') {
33
- throw new TypeError('上传缓存必须是 JSON 对象');
34
- }
35
- uploadJson = stored;
36
- } catch (error) {
37
- console.warn('上传缓存已损坏,将重建缓存:', error);
38
- uploadJson = {};
39
- writeUploadJson();
40
- }
18
+ uploadJson = fs.readJSONSync(uploadJsonDir);
41
19
  }
42
20
 
43
21
  module.exports = {