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 +2 -0
- package/build/prod.js +2 -2
- package/build/utils/createCompilerHandler.js +21 -1
- package/build/utils/getWxmlAssets.js +48 -43
- package/build/utils/upload.js +45 -18
- package/package.json +2 -1
- package/test-support/mini-program.js +49 -0
- package/test-support/project.js +25 -0
- package/test-support/webpack.js +80 -0
package/README.md
CHANGED
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
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
try {
|
|
30
|
+
let res = result;
|
|
31
|
+
if (err) {
|
|
32
|
+
const fallbackPath = path.resolve(resolveContext, request);
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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:
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
75
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
+
const assetsImports = [];
|
|
80
|
+
const wxmlsImports = [];
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
104
|
+
resolve([assetsImports, wxmlsImports]);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
reject(error);
|
|
107
|
+
}
|
|
103
108
|
},
|
|
104
109
|
});
|
|
105
110
|
parser.write(content);
|
package/build/utils/upload.js
CHANGED
|
@@ -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
|
-
(
|
|
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
|
-
(
|
|
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.
|
|
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
|
+
};
|